strcpy如何改变string的值

How strcpy changes the value of string

我做了这个作业来决定下面的代码将做什么(在纸上,没有在计算机上测试)。

char s1[]="Short Message Service", *s2, *s3;
s2=strchr(s1,'M');
s3=strchr(s2,'S');
strncpy(s1+1,s2,1);
strcpy(s1+2,s3);

当我想检查我是否做对时,我在电脑上 运行 它并得到了这个结果:

s1 = SMservice 

s2 = ice

s3 = Service

我以为 s2 会是 "Message Service" 但它变成了 "ice"。显然它在 strcpy(s1+2,s3) 被调用后发生了变化;有人可以解释为什么以及如何影响该功能 s2 吗?

答案是“未定义的行为”——任何事情都有可能发生。 strcpy()strncpy() 的参数不得重叠。 — 然而在这里,strcpy() 的论点确实重叠。

C11 §7.24.2.3 The strcpy function ¶2:

The strcpy function copies the string pointed to by s2 (including the terminating null character) into the array pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.

§7.24.2.4 The strncpy function ¶2

The strncpy function copies not more than n characters (characters that follow a null character are not copied) from the array pointed to by s2 to the array pointed to by s1.308) If copying takes place between objects that overlap, the behavior is undefined.

308) Thus, if there is no null character in the first n characters of the array pointed to by s2, the result will not be null-terminated.

这意味着没有可以给出的可靠答案。您可能会决定然后描述如果复制操作从源头开始复制到目标会发生什么,这可能是您的教师所期望的。 但这不是保证的行为。

给定以下代码和从左到右的复制假设:

char s1[] = "Short Message Service";
char *s2 = strchr(s1, 'M');
char *s3 = strrchr(s2, 'S');
strncpy(s1+1, s2, 1);
strcpy(s1+2, s3);

我们可以推断出 s2 指向 &s1[6]s3 指向 &s1[14] (这是强制性的)。各个阶段s1中的值为:

s1 = "Short Message Service"   -- initial text
s1 = "SMort Message Service"   -- after strncpy
s1 = "SMService"               -- after strcpy (but this assumes UB works as expected)

所以从 s2 开始的字符串现在包含 ice,如您所见。

但是,必须再次强调,这不是必需的行为。

其他答案已经告诉你一个痛苦的事实:strncpystrcpy 复制重叠的字符串是未定义的行为,应该避免,尤其是当涉及到更复杂的格式时(对于 sprintf 等函数也是如此)。


无论如何,所看到的可以通过逐步分析你的代码来解释。我想再次强调,当存在未定义的行为时,任何编译器都可以选择不同的行为,因此我们不能确定这是一个普遍的解释。

需要考虑的重要一点是所有指针共享相同的内存位置。 s1

初始化后
char s1[]="Short Message Service", *s2, *s3;

它指向的char数组是这样的:

----------------------------------------------
|S|h|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e|[=11=]|
----------------------------------------------
 ^
 s1

那你在第二个和第三个字的开头设置s2s3

s2=strchr(s1,'M');
s3=strrchr(s2,'S');

三个指针的位置如下

----------------------------------------------
|S|h|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e|[=13=]|
----------------------------------------------
 ^           ^               ^
 s1          s2              s3

因为每个字符串实际上都是从各自的指针到第一个终止符的数组,如果您打印三个字符串,您会看到:

s1: "Short Message Service"
s2: "Message Service"
s3: "Service"

然后在 s1 的第一个字符之后只复制 s2 的一个字符:

strncpy(s1+1,s2,1);

请注意,当源字符串长于传递给 strncpy 的最大长度时不会复制字符串终止符。该数组将如下所示:

----------------------------------------------
|S|M|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e|[=16=]|
----------------------------------------------
 ^           ^               ^
 s1          s2              s3

打印字符串不会有太大变化:s1 只是变成了 "Short Message Service"。最后你用

strcpy(s1+2,s3);

-----------------------------------------------
|S|M|S|e|r|v|i|c|e|[=17=]|a|g|e| |S|e|r|v|i|c|e|[=17=]|
-----------------------------------------------
 ^           ^                ^
 s1          s2               s3

这就是你得到

的原因

因为每个字符串实际上都是从各自的指针到第一个终止符的数组,如果您打印三个字符串,您会看到:

s1: "SMService"
s2: "ice"       // Because of the terminator in the middle
s3: "Service"   // The original string ending

只是一个实用的建议

如果你需要一个指向每个单词的指针,你只需要存储单词的开头,就像你已经做的那样,然后在 string terminator 的位置放置一个每个space。

这样,s1就会变成"Short"(因为结束符会在第一个space所在的地方找到),s2就会变成"Message"(因为终止符将在第二个 space 所在的位置找到)并且 s3 将是 "Service"(因为原始终止符)。

顺便说一句:这就是 strtok 所做的:查找标记的出现,在其中放置一个字符串终止符并返回经过它的指针。