它不会直接产生任何异常。它会生成代码,当堆栈扩大一页以上时,会生成对新分配区域中的每一页的读写访问。这就是它所做的全部。示例:
extern void bar(char *);
void foo(void)
{
char buf[4096 * 8];
bar(buf);
}
编译(使用 gcc 4.9,在 x86-64 上,-O2)到:
foo:
pushq %rbp
movq $-32768, %r11
movq %rsp, %rbp
subq $4128, %rsp
addq %rsp, %r11
.LPSRL0:
cmpq %r11, %rsp
je .LPSRE0
subq $4096, %rsp
orq $0, (%rsp)
jmp .LPSRL0
.LPSRE0:
addq $4128, %rsp
leaq -32768(%rbp), %rdi
call bar
leave
ret
orq $0, (%rsp) 对(%rsp) 处的内存内容没有影响,但 CPU 将其视为对该地址的读写访问。 (我不知道为什么 GCC 在循环期间将 %rsp 偏移了 4128 个字节,或者为什么它认为帧指针是必要的。)
理论上,如果堆栈变得太大,操作系统可以注意到这些访问并做一些适当的事情。对于 POSIX 兼容的操作系统,这将是一个 SIGSEGV 信号的传递。
您可能想知道操作系统如何注意到这样的事情。硬件允许操作系统将地址空间页面指定为完全不可访问;任何在这些页面中读取或写入内存的尝试都会触发硬件故障,操作系统可以按照它认为合适的方式处理该故障(同样,对于符合 POSIX 的操作系统,提供SIGSEGV)。这可用于在为堆栈保留的空间末尾立即放置一个“保护区”。这就是为什么每页访问一次就足够了。
-fstack-check 旨在保护您免受“保护区域”非常小的情况(可能只有一页)的影响,因此在堆栈上分配一个大缓冲区会移动堆栈指针 过去该区域并进入可访问 RAM 的另一个区域。如果程序碰巧从不接触保护区内的内存,您不会立即崩溃,但您会在其他区域的任何地方乱涂乱画,从而导致延迟操作故障。