C++程序的堆栈大小是如何确定的?

3

我知道最大堆栈大小通常在链接时固定(可能在Windows上是这样)。

但是我不知道程序堆栈大小(仅限已使用的堆栈大小,而非最大堆栈大小)何时被固定到操作系统中。是编译时?链接时?执行时?

就像这样:

int main(){ int a[10]; return 0;}

该程序只使用了10 * sizeof(int)的堆栈。那么,堆栈大小是固定的吗?
首先,如果在malloc或free时更改堆大小呢?

1
它是在进程执行时由操作系统分配的。 - πάντα ῥεῖ
10
值得一提的是,栈的大小并非固定不变的,它会在运行时按需扩展,但栈的限制是固定的。 - Sourav Ghosh
3
在我看来,这个问题极其不清晰。请考虑把每个问号放在适当的位置上,即放在一个良好形式和易读的问题结尾处。例如,“so”不是一个问题,“if the size is fixed”也不是一个问题。 - barak manos
谢谢你的回答。我只是在公司面试时被问到了这个问题。 - Alps1992
2
好的...“所以,如果大小是固定的”也不是一个问题!!! - barak manos
显示剩余3条评论
2个回答

2

程序被加载时,操作系统并没有显式地提供堆栈大小。相反,操作系统使用页面错误的机制(如果MMU支持)。

如果您试图访问尚未被操作系统授予的内存,则MMU会生成一个页面错误,该错误由操作系统处理。操作系统检查页面错误的地址,并创建新的内存页以扩展堆栈,或者如果您已经耗尽了堆栈限制,则将其处理为堆栈溢出

考虑在x86和Linux上运行以下程序:

void foo(void) {
    volatile int a = 10;
    foo();
}

int main() {
    foo();
}

由于无限递归和栈溢出,程序会出现故障。实际上,需要无限的堆栈才能完成。当程序加载时,操作系统分配初始堆栈并将其写入%rsp(堆栈指针)。让我们来看一下foo()的反汇编:

push   %rbp
mov    %rsp,%rbp         <--- Save stackpointer to %rbp
sub    $0x10,%rsp        <--- Advance stack pointer by 16 bytes
movl   $0xa,-0x4(%rbp)   <--- Write memory at %rbp
callq  0x400500 <foo>
leaveq 
retq 

foo()被调用了最多4096 / 16 = 256次时,你将会在地址X + 4096处写入内存,其中X是初始的%rsp值,这会导致越界。此时会产生页错误,操作系统会为栈提供新的内存页,以便程序继续使用。

foo()被调用了约500k次(默认Linux ulimit的栈大小限制)后,操作系统会检测到应用程序使用了过多的栈页,并向其发送SIGSEGV信号。


0

在回答一个问题时,我提供了以下信息:

BSS/DATA段包含所有全局变量,这些变量被初始化为特定值或默认为零。该段是可执行映像的一部分。在加载时,堆段被添加到其中;然而,它不是一个“段”,而只是要作为已加载的BSS/DATA段的扩展分配的额外数据量。同样,栈“段”也不是真正的段,而是添加到BSS+堆段中。栈向下增长,而堆向上增长。如果它们重叠(使用更多堆并且栈仍在增长),则会发生“内存不足”错误(堆)或“堆栈溢出”(栈)- 可以通过使用段寄存器(Intel)触发硬件生成的异常或使用软件检查来检测到这种情况。

这是布置段的传统方式。想象一下旧的英特尔芯片,所有程序数据必须在64KB中。对于更现代的芯片,通常使用相同的布局,在此布局中使用32MB的地址空间,但只使用实际需要的物理内存。因此,栈可以非常大。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接