2

这是 ac 程序的摘录,应该演示缓冲区溢出。

void foo()
{
  char arr[8];
  printf(" enter bla bla bla");
  gets(arr);
  printf(" you entered %s\n", arr);
}

问题是“用户最多可以输入多少个输入字符而不产生缓冲区溢出”

我最初的答案是 8,因为 char 数组有 8 个字节长。虽然我很确定我的答案是正确的,但我尝试了更多的字符,发现在出现分段错误之前我可以输入的字符数限制为 11。(我在 VirtualBox Ubuntu 上运行它)

所以我的问题是:为什么可以在 8 字节数组中输入 11 个字符?

4

5 回答 5

6

您的字符实际上超出了定义数组的范围,导致未定义的结果。直到您覆盖一些正在用于其他用途的内存时,您才会看到效果。

语言和运行时没有做任何事情来防止您溢出缓冲区,这正是这些错误如此糟糕并且有时难以追踪的原因。

由于这些原因,gets为了更安全的函数(在这种情况下),这些函数被弃用了,这些函数getline要求它们存储数据的数组的长度。

见:http ://crasseux.com/books/ctutorial/gets.html

此外,您只能可靠地存储 7 个字符,因为您需要第 8 个字符作为空终止符。

于 2010-06-08T19:28:13.320 回答
3

可能是因为对齐和/或填充。那里可能有一些“备用”内存,实际上并没有使用,所以当你覆盖它时,什么都没有。这并不意味着它是正确的或它有效,只是它现在不会失败,对你来说,在那台机器上使用那个编译器、椅子、头发颜色等等。

于 2010-06-08T19:28:41.947 回答
3

(我在 VirtualBox Ubuntu 上运行它)所以我的问题是:为什么可以在 8 字节数组中输入 11 个字符?

11+1 表示零终止 = 12 个字符。当gets() 将13 个字符写入arr[8] 时发生IOW 崩溃。

您尚未发布精确的堆栈跟踪,但根据我的经验,它应该在 foo() 返回后崩溃。

堆栈帧(带有 for void foo() + gets())看起来像 (*):

  • <低内存地址>
  • 获取()局部变量
  • 在 gets() 调用时保存的堆栈指针(所谓的“序言”)
  • 返回地址,指向 foo()
  • foo() 局部变量(你的 char arr[8])
  • 在 gets() 调用时保存的堆栈指针
  • 返回地址,指向 foo() 的调用者
  • <更高的内存地址>

从所有信息中,最重要的位是返回地址和保存的堆栈指针。在您的情况下,写入第 13 个字节可能已经损坏了 foo() 函数的已保存堆栈指针。很可能调用以下 printf() 会成功,因为堆栈指针仍然有效(最后通过从 gets() 返回更改)。但是从 foo() 返回会导致 foo() 保存的堆栈指针(现在已损坏)被恢复,然后从调用函数内部访问堆栈的任何操作都会转到错误的地址。

根据我的经验,这是最可能的情况。当堆栈损坏时,很难确定会发生什么。

(*) 有关如何构建堆栈帧的详细信息,请查找 ABI - 应用程序二进制接口 - 适用于您的架构:例如 Intel i386 的 IA-32 ABI 或 AMD64 的 AMD64 ABI。

于 2010-06-08T20:50:47.037 回答
1

恰好有足够的空闲内存供您存储额外的数据。您永远不应该依赖它并始终将数据保持在数组的范围内。

您的示例中实际允许的字符数是 7 个“标准”字符加上 NULL 字符(总共 8 个)。

于 2010-06-08T19:32:51.690 回答
1

在 C 中,没有什么可以阻止您越过数组的末尾。当你将字符串放入内存时,它会填满你的数组,然后继续填充内存,直到有东西真正停止它。在较大的程序中,这可能意味着覆盖内存中的其他变量,这会导致难以追踪的错误。在单独的注释中,您可能想考虑 C 在确定字符串的大小将适合内存位置时如何找到字符串的结尾。

于 2010-06-08T19:32:57.913 回答