为什么在这种特定情况下在堆栈上分配大元素不会失败?

Nis*_*sky 5 c arrays stack segmentation-fault

int在C中在堆栈上分配一个大型数组时,程序执行时没有错误.但是,如果我事先在堆栈上初始化变量,则会发生段错误(可能是因为大型数组超出了堆栈大小).如果在声明数组后初始化变量,这对我来说是有意义的.是什么导致这种行为,记忆明智?

我的印象是,只需在堆栈上声明一个变量,就会分配所需的空间,导致在分配非常大的数据类型时立即崩溃.

我怀疑它与编译器优化它有关,但它没有意义,考虑到我foo在第二个例子中也没有改变.

我正在使用gcc 7.2.0进行编译,没有设置任何标志.在Ubuntu 17.10上执行.

这运行没有错误:

int main(){
  int i;
  unsigned char foo [1024*1024*1024];
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

这会立即崩溃:

int main(){
  int i = 0;
  unsigned char foo [1024*1024*1024];
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

有人能告诉我这里发生了什么吗?

Die*_*Epp 6

注意:以下是实现细节.C标准不包括此内容.

崩溃不是由分配空间引起的.崩溃是由于写入不可写的页面或从不可读的页面读取而导致的.

您可以看到声明实际上不需要读取或写入任何内存,不一定:

int i;
Run Code Online (Sandbox Code Playgroud)

但如果它已初始化,则必须写入值:

int i = 0;
Run Code Online (Sandbox Code Playgroud)

这会触发崩溃.请注意,确切的行为取决于您使用的编译器和您拥有的优化设置.不同的编译器会以不同的方式分配变量,以及优化的编译器通常会同时删除i,并foo从功能完全,因为他们不需要.某些编译器还会在某些配置下将变量初始化为垃圾值,以帮助调试.

分配堆栈空间只需要更改堆栈指针,这是一个寄存器.如果分配的堆栈空间过多,堆栈指针将指向无效的内存区域,程序在尝试读取或写入这些地址时会发生段错误.大多数操作系统都有"保护页面",因此有效内存不会放在堆栈旁边,以确保程序在大多数情况下成功崩溃.

以下是Godbolt的一些输出:

main:
  push rbp
  mov rbp, rsp
  sub rsp, 1073741720        ; allocate space for locals
  mov DWORD PTR [rbp-4], 0   ; initialize i = 0
  mov eax, 0                 ; return value = 0
  leave
  ret
Run Code Online (Sandbox Code Playgroud)

请注意,此版本不会崩溃,因为i它位于堆栈的顶部(向下增长).如果i放在堆栈的底部,这可能会崩溃.编译器可以按任何顺序自由地将变量放在堆栈中,因此它是否真的崩溃将在很大程度上取决于您使用的特定编译器.

您还可以更清楚地看到分配不会崩溃的原因:

; Just an integer subtraction. Why would it crash?
sub rsp 1073741720
Run Code Online (Sandbox Code Playgroud)

  • @Nisky:编译器没有"重新排序"任何东西,因为变量实际上并没有任何类型的顺序.如果将字段放在结构中,则字段*do*有一个顺序,但变量根本没有顺序. (2认同)