考虑下面的程序

    char str[5];
    strcpy(str,"Hello12345678");
    printf("%s",str);

运行该程序时会出现分段错误。

但是当 strcpy 替换为以下时,程序运行正常。

strcpy(str,"Hello1234567");

所以问题是,当尝试将长度超过 5 个字符的任何其他字符串复制到 str 时,它应该会崩溃。

那么为什么它不会因“Hello1234567”而崩溃,而只会因“Hello12345678”(即长度为 13 或超过 13 的字符串)而崩溃。

该程序在 32 位机器上运行。

有帮助吗?

解决方案

有三种类型的行为标准,你应该会感兴趣。

1 / 定义的行为。这将在所有符合实施工作。自由地使用此

2 / 实现定义的行为。如前所述,这取决于实现,但至少它仍然定义。实现都必须记录他们在这种情况下做什么。使用这个,如果你不关心可移植性。

3 / 未定义行为。任何事情都有可能发生。我们的意思的任何的,直至并包括整个计算机折叠成一个裸奇点和吞咽本身,你和你的同事的相当大的比例。千万不要用这个。永远!认真!不要让我到那儿。

复制多于4个字符和一个零字节到char[5]是未定义的行为。

严重的是,它并不重要,为什么你的程序有14个字符的崩溃而不是13,你几乎可以肯定覆盖在堆栈上一些非崩溃的信息和你的程序将最有可能产生反正不正确的结果。事实上,坠机是更好,因为至少它阻止你依靠可能的不良影响。

增加数组的大小的东西更合适的(char[14]在这种情况下与可获得的信息),或使用一些其他的数据结构,其能够应付。


更新

既然你似乎很关心找出为什么一个额外的7个字符不会出现问题,但8个字呢,让我们设想在进入main()可能的栈布局。我说“可能”,因为实际的布局取决于你的编译器使用调用约定。由于C启动代码调用与main()argc argv,堆叠在main()的开始,对于char[5]分配空间后,可能看起来像这样:

+------------------------------------+
| C start-up code return address (4) |
| argc (4)                           |
| argv (4)                           |
| x = char[5] (5)                    |
+------------------------------------+

当你写的字节Hello1234567\0用:

strcpy (x, "Hello1234567");

x,它覆盖argcargv但是,从main()回报,那也没关系。具体地说Hello填充x1234填充argv567\0填充argc。只要不实际尝试的使用后argc和/或argv,你会好起来的:

+------------------------------------+ Overwrites with:
| C start-up code return address (4) |
| argc (4)                           |   '567<NUL>'
| argv (4)                           |   '1234'
| x = char[5] (5)                    |   'Hello'
+------------------------------------+

不过,如果你写Hello12345678\0(注意额外的“8”),以x,它覆盖argcargv和也的返回地址的一个字节,这样,当main()尝试返回的C启动代码,它熄灭成童话土地代替:

+------------------------------------+ Overwrites with:
| C start-up code return address (4) |   '<NUL>'
| argc (4)                           |   '5678'
| argv (4)                           |   '1234'
| x = char[5] (5)                    |   'Hello'
+------------------------------------+

同样,这完全取决于你的编译器的调用约定。这是可能不同的编译器会直到你又写了三个大字总是垫出阵列的4个字节的倍数和代码不会失败在那里。即使是相同的编译器可堆栈帧分配不同的变量,以确保对准是满意的。

这就是他们的意思是不确定的:你没有的知道的有什么事情发生。

其他提示

您要复制到堆栈,所以它依赖于什么编译器放置在堆栈中,额外的数据多少需要你的程序崩溃。

一些编译器可能会产生的代码,将只在缓冲区大小的单个字节崩溃 - 这是未定义的行为是什么

我猜大小13就足以覆盖返回地址,或类似的东西,这时候你的崩溃函数返回。但另一种编译器或其他平台可以/将与不同长度的崩溃。

此外,您的程序可能有不同的长度崩溃,如果它跑更长的时间,如果事情不那么重要了被覆盖。

对于32位Intel平台的解释如下。当你声明的char [5]在堆栈上的编译器确实分配,因为排列8个字节。然后,它的典型的功能,以具有以下序幕:

push ebp
mov ebp, esp

此节省了堆栈EBP注册表值,然后移动ESP寄存器值到EBP用于使用ESP值来访问参数。这导致在堆4多个字节与EBP值占据。

在后记EBP被恢复,但其值通常仅用于访问堆栈分配的函数的参数,所以覆盖它可能不是在大多数情况下受到伤害。

所以,你有如下的布局(堆栈上英特尔向下增长):8个字节为您的阵列,然后4个字节EBP,然后通常返回地址

这就是为什么你需要覆盖至少13个字节到你的程序崩溃。

要添加到上面的答案:可以测试等这些缺陷用工具如 Valgrind的。如果您使用的是Windows,看看这个SO线程

它取决于“STR”阵列后的堆栈上什么。你只是碰巧不被任何东西重要的践踏,直到您复制的字符。

所以,这将取决于还有什么是函数,编译器使用,并可能编译选项了。

13 5 + 8,表明有与STR阵列后的两个非临界的话,那么一些关键(也许返回地址)

这是未定义行为(UB)的纯净的美:它未定义

您的代码:

char str[5];
strcpy(str,"Hello12345678");

写入14字节/字符到str只能容纳5个字节/字符。这调用UB。

  

由于该行为是未定义。   使用strncpy()函数。看到这个页面    http://en.wikipedia.org/wiki/Strcpy   获得更多信息。

函数strncpy是不安全的,因为它不添加如果源串具有长度的NULL终止> = n,其中n是目标串的大小。

char s[5];
strncpy(s,5,"test12345");
printf("%s",s); // crash

我们总是用是strlcpy来缓解这个。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top