我制作了一个看起来像这样的小程序:
void foo () {
char *str = "+++"; // length of str = 3 bytes
char buffer[1];
strcpy (buffer, str);
cout << buffer;
}
int main () {
foo ();
}
我原本以为会出现堆栈溢出异常,因为缓冲区的大小比str小,但是它成功打印出+++ ...有人可以解释为什么会发生这种情况?
非常感谢你。
答案 0 :(得分:13)
未定义的行为(UB)发生了,你不幸的是它没有崩溃。
写入超出已分配内存的范围是Undefined Behavior,UB不保证崩溃。什么都可能发生。
未定义的行为意味着无法定义行为。
答案 1 :(得分:2)
你没有得到堆栈溢出,因为它是未定义的行为,这意味着任何都可能发生。
今天许多编译器都有特殊标志,告诉他们插入代码来检查一些堆栈问题,但是你经常需要明确告诉编译器启用它。
答案 2 :(得分:2)
未定义的行为......
如果您真的关心为什么在这种情况下很有可能获得“正确”的结果:有几个因素。具有auto
存储类的变量(即,正常,局部变量)通常将在堆栈上分配。在典型情况下,堆栈上的所有项目都是某个特定大小的倍数,通常是int
- 例如,在典型的32位系统上,您可以在堆栈上分配的最小项目将是32位。换句话说,在典型的32位系统上,如果您更喜欢该术语,则需要四个字节(四个char
s)。
现在,正好相反,您的源字符串只包含3个字符,加上NUL终结符,总共4个字符。通过纯粹的坏机会,恰好足够短以适应空间,编译器(有点)被迫为buffer
分配,即使你告诉它分配更少。
但是,如果您将更长的字符串复制到目标(可能甚至只是单个字节/字符更长),则可能会出现重大问题 (虽然在64位软件中,你可能还需要更长的时间)。
还有另外一点需要考虑:根据系统和堆栈增长的方向,您可以将井写入您分配的空间的末尾,并且仍然可以使用似乎工作。您已在buffer
中分配了main
。 main
中定义的唯一其他内容是str
,但它只是一个字符串文字 - 所以很可能没有实际分配空间来存储字符串文字的地址。你最终得到了静态分配的字符串文字(不在堆栈上),并且在你使用str
的地方替换了它的地址。因此,如果您在buffer
的末尾写入,则可能只是写入堆栈顶部的任何空间。在典型情况下,堆栈将一次分配一页。在大多数系统中,页面大小为4K或8K,因此对于堆栈中使用的随机空间量,您可以分别平均获得2K或4K的空间。
实际上,由于这是在main
并且没有其他任何内容被调用,你可以预期堆栈几乎是空的,所以很有可能在顶部附近有一整页未使用的空间。堆栈,因此将字符串复制到目标可能会起作用,直到/除非源字符串相当长(例如,几千字节)。
至于为什么它经常会比这更快失败:在典型情况下,堆栈向下增长,但buffer[n]
使用的地址将向上增长。在典型情况下,堆栈“上方”buffer
上的下一个项目将是从main
到调用main
的启动代码的返回地址 - 因此,只要您编写超过buffer
堆栈上的空间量(如上所述,可能比您指定的要大),最终会覆盖main
的返回地址。在这种情况下,main
中的代码通常看起来可以正常工作,但是一旦执行(尝试)从main返回,它最终将使用您刚刚写入的数据作为返回地址,指出你更有可能看到明显的问题。
答案 3 :(得分:1)
概述会发生什么:
要么你很幸运,它会立即崩溃。 或因为技术上未定义,您最终可能会写入其他内容使用的内存地址。假设您有两个缓冲区,一个buffer[1]
和一个longbuffer[100]
,并假设buffer[2]
的内存地址可能与longbuffer[0]
相同,这意味着{{1}现在终止于long buffer
(因为空终止)。
longbuffer[1]
希望有助于澄清请注意这些内存地址在随机情况下不太可能相同,但它可能会发生,并且它不会发生甚至需要是相同的类型,在被赋值覆盖之前,任何东西都可以在char *s = "+++";
char longbuffer[100] = "lorem ipsum dolor sith ameth";
char buffer[1];
strcpy (buffer, str);
/*
buffer[0] = +
buffer[1] = +
buffer[2] = longbuffer[0] = +
buffer[3] = longbuffer[0] = \0 <- since assigning s will null terminate (i.e. add a \0)
*/
std::cout << longbuffer; // will output: +
和buffer[2]
地址。然后,当你下次尝试使用你的(现在被破坏的)变量时,它可能会崩溃,而那时调试变得有点乏味,因为崩溃似乎与真正的问题有很大关系。 (即当你试图访问堆栈中的变量时崩溃,而真正的问题是你的代码中的其他地方破坏了它)。
答案 4 :(得分:0)
没有明确的边界检查,或者在strcpy
上抛出异常 - 它是一个C函数。如果你想在C ++中使用C函数,你将不得不承担检查边界等的责任或转而使用std::string
。
在这种情况下,它确实有效,但在一个关键系统中,采用这种方法可能意味着你的单元测试通过但是在生产中,你的代码barfs - 而不是你想要的情况。
答案 5 :(得分:0)
堆栈损坏正在发生,它是一个未定义的行为,幸运的是没有发生崩溃。在程序中执行以下修改并运行它会因堆栈损坏而确实崩溃。
void foo () {
char *str = "+++"; // length of str = 3 bytes
int a = 10;
int *p = NULL;
char buffer[1];
int *q = NULL;
int b = 20;
p = &a;
q = &b;
cout << *p;
cout << *q;
//strcpy (buffer, str);
//Now uncomment the strcpy it will surely crash in any one of the below cout statment.
cout << *p;
cout << *q;
cout << buffer;
}