c - 为什么 stackoverflow 错误乱七八糟?

标签 c recursion operating-system stack-overflow

这个简单的 C 程序很少在相同的调用深度处终止:

#include <stdio.h>
#include <stdlib.h>

void recursive(unsigned int rec);

int main(void)
{
  recursive(1);
  return 0;
}

void recursive(unsigned int rec) {
    printf("%u\n", rec);
    recursive(rec + 1);
}

这种困惑行为背后的原因可能是什么?

我正在使用 fedora(16GiB 内存,堆栈大小为 8192),并使用没有任何选项的 cc 编译。

编辑

  • 我知道这个程序会抛出 stackoverflow
  • 我知道启用某些编译器优化会改变行为并且程序会达到整数溢出。
  • 我知道这是未定义的行为,这个问题的目的是了解/大致了解实现特定的内部行为,这可能解释我们在那里观察到的情况。

问题更多,鉴于在 Linux 上线程堆栈大小是固定的并由 ulimit -s 给定,什么会影响可用堆栈大小,以便 stackoverflow 不会总是同时发生调用深度?

编辑 2 @BlueMoon 在他的 CentOS 上总是看到相同的输出,而在我的 Fedora 上,堆栈为 8M,我看到不同的输出(最后打印的整数 261892 或 261845,或 261826,或...)

最佳答案

将 printf 调用更改为:

printf("%u %p\n", rec, &rec);

这会强制 gcc 将 rec 放入堆栈并为您提供它的地址,这是堆栈指针正在发生的事情的一个很好的指示。

运行你的程序几次,注意最后打印的地址发生了什么。在我的机器上运行几次显示:

261958 0x7fff82d2878c
261778 0x7fffc85f379c
261816 0x7fff4139c78c
261926 0x7fff192bb79c

首先要注意的是堆栈地址总是以78c79c 结尾。这是为什么?我们应该在跨越页面边界时崩溃,页面的长度为 0x1000 字节,每个函数占用 0x20 字节的堆栈,因此地址应以 00X 或 01X 结尾。但是仔细观察,我们在 libc 中崩溃了。所以堆栈溢出发生在 libc 的某个地方,由此我们可以得出结论,调用 printf 和它调用的所有其他东西至少需要 0x78c = 1932(可能加上 X*4096)字节的堆栈才能工作。

第二个问题是为什么到达栈尾需要不同的迭代次数?事实上,每次运行程序时我们获得的地址都是不同的。

1 0x7fff8c4c13ac
1 0x7fff0a88f33c
1 0x7fff8d02fc2c
1 0x7fffbc74fd9c

栈在内存中的位置是随机的。这样做是为了防止整个系列的缓冲区溢出攻击。但是由于内存分配,尤其是在这个级别,只能在多个页面(4096 字节)中完成,所有初始堆栈指针都将在 0x1000 处对齐。这将减少随机堆栈地址中的随机位数,因此通过在堆栈顶部浪费随机字节数来增加额外的随机性。

操作系统只能在整个页面中计算您使用的内存量,包括堆栈上的限制。因此,即使堆栈从随机地址开始,堆栈中最后可访问的地址也始终是一个以 0xfff 结尾的地址。

简短的回答是:为了增加随机内存布局中的随机性,堆栈顶部的一堆字节被故意浪费,但堆栈的末尾必须在页面边界结束。

关于c - 为什么 stackoverflow 错误乱七八糟?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31180563/

相关文章:

c - 用 C 语言调整输出终端的大小

Java计算器 - 调车场

php - 如何递归地向每个数组添加键?

java程序使操作系统难以格式化可移动磁盘

C 程序链表语法

C编程: array

C编程初学者问题

javascript - 返回变量和函数(递归)

linux - 为什么 linux 和 unix 的中断服务方式存在设计差异?

php - 在 PHP 中检索浏览器和操作系统