碰到一道题目:

1
2
3
4
5
6
7
char s[]="123456789";
char d[]="123";

strcpy(d,s);

cout<<s<<endl;
cout<<d<<endl;

问输出结果。

答案是比较奇怪的 :

56789
123456789

按常理应该是两者都输出 123456789。strcpy()的函数原型为 char __cdecl strcpy(char, const char *),从网上流出的 strcpy()源码无法分析出端倪,考虑到本例唯一的特殊点在于违背了 strcpy()要求目的字符串要有足够空间容纳复制的内容的规定。所以从此处入手。

在 VC++6.0 编译器下调试后发现

执行 strcpy()前:

before

strcpy 后:

after

从两张图中可以看出&d[3]+1 处的内存地址便是 s[]的起始地址,所以得出一种假想,编译器因为 d[]中本无法存放‘5’-‘\0’的 6 位字符,而本例中 d[]与 s[]分配的内存连续,即复制 s[]到 d[]时数组赋值发生越界,借用了接下来的内存物理地址 0x0013ff74-0x0013ff79(也就是 s[0]-s[5]的空间)存放来‘5’-‘\0’。而输出的 s[]时由于输出函数碰到‘\0’即终止,所以只输出 56789。

为验证这种假想,尝试在 s[]和 d[]之间栈上分配其他内存或者颠倒 d[]和 s[]的定义顺序以破坏这种内存的连续性,输出结果果然为正常显示了。

before

到此似乎得出一种结论,当 strcpy()的目的字符串定义紧跟在源原字符串定义后面时,由于“分配的内存相连续”,目的字符串 d[]的输出结果正常,而源字符串 s[]的输出结果会是从 s[strlen(d)+1]往后的字符。若两者分配的内存真是相连,因为会发生覆盖且输出函数是碰到‘\0’就结束相信会真的如此结论所说,但是对定义顺序相邻近就内存分配连续有所怀疑,对此做了下面的实验。

after

只是改动了下 d[]数组分配的空间大小为 5B,则输出结果不一致。

调试后发现:

strcpy 前:

before

strcpy 后:

after

由图中可推测,d[]和 s[]虽定义相邻但未内存相连续,d[0]-d[4]分别存放字符‘1’-‘4’,而在接下来的内存 0x0013ff71-0xff001373 中分别复制存放字符‘5’-‘8’,从而在内存 0x0013ff74-0x0013ff75 也就是 s[0]和 s[1]中覆盖存放‘9’和‘\0’,所以自然输出 s[]为 9。

继续实验后发现尝试给 d 分配的内存为 6,7,8 时 s[]均输出为 9,并不是很随机,调试后原因相仿,而当给 d[]分配的内存>=9 时则输出结果为 123456789 正常显示。

其中似乎有一种巧合性,在分配给目的字符串 d[]的空间,大于实际数据的大小(包括‘\0’,本例中“123”为 4,定义为 d[]或 d[4])且小于源字符串 strlen(s)(不包括‘\0’,本例为 9)时,编译器给相邻两者分配空间之间的间隙总是正好足够复制到倒数第三位字符(也仅是本例),本例中即可以复制到字符‘8’,而字符‘9’和‘\0’则需要借 s[]的空间复制。

而当 s[]改为“12345678”时,即 strlen(s)=8 时,给 d[]的内存空间为 5,6,7 时,s[]的输出结果都为空,从上面可推测出,此时在”分配给目的字符串 d[]的空间大于实际数据的大小且小于源字符串 strlen(s)”时,编译器给相邻两者分配空间之间的间隙该总是正好足够复制到倒数第二位字符,所以在 s[0]处被覆盖上了’\0’,而给 d 的空间为本身大小即改为 char d[4]=”123”或 char d[4]=”123”,依然是我们上面所推测的“从 s[strlen(d)+1]往后的字符”(5678)。

7

依此可推想,再缩小一字节 s[]的空间如下,定义 d[5]或 d[6]=”123”时该不覆盖 s[]的空间了,也就是输出按常理显示;或者增加 1 字节 s[]的空间,定义为 char s[]=”1234567890”,d[5]或 d[6] d[7] d[8] 或 d[9]=”123”,s[]均输出 90:实验结果也确实如此。其中似乎有另一种规律性。

8

总结:

strcpy()函数当目标字符串 d[]没有足够内存容纳复制时会发生赋值数组越界,这种方式显然不值得提倡,不过编译器并未报错,d[]始终会正常输出为原先的 s[],s[]输出结果未知但部分也隐含着某种规律性:

先看目的字符串 d[]定义是否紧跟在源原字符串 s[]定义后面(栈上定义在后的变量地址值小),

若不是:

复制时自然越界不到 s[]所在内存,s[]则按原先内容显示;

若是:

再看给 d[]分配的空间是否正好为 strlen(d)+1,

是的话,s[]输出为原先“从 s[strlen(d)+1]往后的字符”,

若也不是,看给 d[]分配的空间是否>=strlen(s),大于的话仍是按原先内容显示,

若间于两者之间,由于内存覆盖情况未知,s[]的输出无法判断。(虽然似乎也有某种规律性 :

给 d[] 分配的空间在这个区间内虽不同,覆盖情况却相同从而 s[] 输出也相同。)

原作者: qianhuayeyu
原文链接:https://blog.csdn.net/qianhuayeyu/article/details/8081712