我有这个家庭作业来决定下面的代码将做什么(在纸上,没有在计算机上测试(。
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.3strcpy
函数 ¶2:
strcpy
函数将s2
指向的字符串(包括终止空字符(复制到s1
指向的数组中。如果在重叠的对象之间进行复制,则行为是未定义的。
§7.24.2.4strncpy
函数 ¶2
strncpy
函数从s2
指向的数组复制到s1
指向的数组中不超过n
个字符(不复制空字符后面的字符(。(308(如果在重叠的对象之间进行复制,则行为是不确定的。308(因此,如果
s2
指向的数组的前n
个字符中没有空字符,则结果将不会以空值结尾。
这意味着没有可靠的答案可以给出。 您可能会决定随后描述如果复制操作从源的开头复制到目标会发生什么情况,这是教师可能期望的。 但这不是保证的行为。
给定以下代码和从左到右复制假设:
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
,正如您所发现的。
但是,必须再次强调,这不是必需的行为。
其他答案已经告诉你一个痛苦的事实:复制带有strncpy
和strcpy
的重叠字符串是未定义的行为,应该避免,特别是当涉及到更复杂的格式时(对于sprintf
等函数也是如此(。
无论如何,您所看到的可以逐步解释分析您的代码。我想再次强调,当存在未定义的行为时,任何编译器都可以选择不同的行为,因此我们无法确定这是一个普遍的解释。
要考虑的重要一点是所有指针共享相同的内存位置。初始化s1
后
char s1[]="Short Message Service", *s2, *s3;
它指向的字符数组如下所示:
----------------------------------------------
|S|h|o|r|t| |M|e|s|s|a|g|e| |S|e|r|v|i|c|e| |
----------------------------------------------
^
s1
然后你在第二个和第三个单词的开头设置s2
和s3
:
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| |
----------------------------------------------
^ ^ ^
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| |
----------------------------------------------
^ ^ ^
s1 s2 s3
打印字符串不会有太大变化:s1
只是变得"Short Message Service"
。最后你使用
strcpy(s1+2,s3);
-----------------------------------------------
|S|M|S|e|r|v|i|c|e| |a|g|e| |S|e|r|v|i|c|e| |
-----------------------------------------------
^ ^ ^
s1 s2 s3
这就是为什么你会得到
由于每个字符串实际上是从相应指针到第一个终止符的数组,因此如果您打印三个字符串,您将看到:
s1: "SMService"
s2: "ice" // Because of the terminator in the middle
s3: "Service" // The original string ending
只是一个实用的建议
如果你需要一个指向每个单词的指针,你只需要存储单词的开头,就像你已经做的那样,然后在每个空格的位置放置一个字符串终止符。
这样,s1
将被"Short"
(因为将在第一个空格所在的位置找到终结者(,s2
将被"Message"
(因为将在第二个空格所在的位置找到终结者(和s3
将被"Service"
(因为原始终止符(。
顺便说一下:这就是strtok
的作用:查找标记的出现,在其中放置一个字符串终止符,然后返回指针经过它。