1

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

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

  strcpy (buffer, str);

  cout << buffer;
}

int main () {
  foo ();
}

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

4

6 回答 6

13

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

于 2013-01-03T10:50:19.480 回答
2

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

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

于 2013-01-03T10:51:48.743 回答
2

未定义的行为...

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

现在,碰巧的是,您的源字符串仅包含 3 个字符,加上 NUL 终止符,总共 4 个字符。纯粹是坏机会,它恰好足够短以适合编译器(某种)被迫分配的空间buffer,即使你告诉它分配更少。

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

还有一点需要考虑:根据系统和堆栈增长的方向,您可能能够很好地写入分配的空间的末尾,并且仍然可以正常工作。您已分配buffer. main中定义的唯一另一件事mainstr,但它只是一个字符串文字 - 因此实际上没有分配空间来存储字符串文字的地址。您最终会得到静态分配的字符串文字本身(而不是在堆栈上),并将其地址替换为您使用过的str. 因此,如果你写到末尾buffer,您可能只是在写入堆栈顶部剩余的任何空间。在典型情况下,堆栈将一次分配一页。在大多数系统上,页面大小为 4K 或 8K,因此对于堆栈上使用的随机空间量,您可以预期平均分别有 2K 或 4K 空闲空间。

实际上,由于这是 inmain并且没有调用任何其他内容,因此您可以预期堆栈几乎是空的,因此堆栈顶部有可能接近一整页未使用的空间,因此将字符串复制到目的地可能会工作,直到/除非源字符串长(例如,几千字节)。

至于为什么它通常会比这更快地失败:在典型情况下,堆栈向下增长,但使用的地址buffer[n]会向上增长。在典型情况下,堆栈“上方”的下一项将是调用的启动代码buffer的返回地址- 因此,一旦您写入堆栈上的空间量(如上所述,可能比您指定的要大)您最终会覆盖来自. 在这种情况下,里面的代码通常看起来可以正常工作,但是一旦执行(尝试)从 main 返回,它最终会使用你刚刚写的数据作为返回地址,此时你是一个更有可能看到明显的问题。mainmainbuffermainmain

于 2013-01-03T11:22:29.977 回答
1

概述会发生什么:

要么你很幸运,它会立即崩溃。或者因为它在技术上未定义,您最终可能会写入其他东西使用的内存地址。假设您有两个缓冲区,一个buffer[1]和一个longbuffer[100],并假设内存地址buffer[2]可能与现在终止的相同longbuffer[0]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],这些内存地址在随机情况下不太可能相同,但它可能会发生,甚至不需要是相同的类型,任何东西都可以buffer[3]是被作业覆盖。然后下次你尝试使用你的(现在被破坏的)变量时,它很可能会崩溃,那是调试变得有点乏味的时候,因为崩溃似乎与真正的问题没有太大关系。(即,当您尝试访问堆栈上的变量时它会崩溃,而真正的问题是您代码中的其他地方破坏了它)。

于 2013-01-03T11:20:07.813 回答
0

没有明确的边界检查或异常抛出strcpy——它是一个 C 函数。如果你想在 C++ 中使用 C 函数,你将不得不承担检查边界等的责任,或者切换到使用std::string.

在这种情况下,它确实有效,但在关键系统中,采用这种方法可能意味着您的单元测试通过,但在生产中,您的代码会出错 - 这不是您想要的情况。

于 2013-01-03T10:54:29.613 回答
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;
}
于 2013-01-03T11:25:22.087 回答