C ++中字符串的堆栈溢出?

时间:2013-01-03 10:49:25

标签: c++

我制作了一个看起来像这样的小程序:

void foo () {
  char *str = "+++"; // length of str = 3 bytes
  char buffer[1];

  strcpy (buffer, str);

  cout << buffer;
}

int main () {
  foo ();
}

我原本以为会出现堆栈溢出异常,因为缓冲区的大小比str小,但是它成功打印出+++ ...有人可以解释为什么会发生这种情况?
非常感谢你。

6 个答案:

答案 0 :(得分:13)

未定义的行为(UB)发生了,你不幸的是它没有崩溃。
写入超出已分配内存的范围是Undefined Behavior,UB不保证崩溃。什么都可能发生。
未定义的行为意味着无法定义行为。

答案 1 :(得分:2)

你没有得到堆栈溢出,因为它是未定义的行为,这意味着任何都可能发生。

今天许多编译器都有特殊标志,告诉他们插入代码来检查一些堆栈问题,但是你经常需要明确告诉编译器启用它。

答案 2 :(得分:2)

未定义的行为......

如果您真的关心为什么在这种情况下很有可能获得“正确”的结果:有几个因素。具有auto存储类的变量(即,正常,局部变量)通常将在堆栈上分配。在典型情况下,堆栈上的所有项目都是某个特定大小的倍数,通常是int - 例如,在典型的32位系统上,您可以在堆栈上分配的最小项目将是32位。换句话说,在典型的32位系统上,如果您更喜欢该术语,则需要四个字节(四个char s)。

现在,正好相反,您的源字符串只包含3个字符,加上NUL终结符,总共4个字符。通过纯粹的坏机会,恰好足够短以适应空间,编译器(有点)被迫为buffer分配,即使你告诉它分配更少。

但是,如果您将更长的字符串复制到目标(可能甚至只是单个字节/字符更长),则可能会出现重大问题 (虽然在64位软件中,你可能还需要更长的时间)。

还有另外一点需要考虑:根据系统和堆栈增长的方向,您可以将写入您分配的空间的末尾,并且仍然可以使用似乎工作。您已在buffer中分配了mainmain中定义的唯一其他内容是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;
}