在执行 C 到 Intel x86 程序集转换时,堆栈上的数组分配比所需的多 space

Array allocation on stack taking more space than required while doing C to Intel x86 Assembly Conversion

我正在学习 C 和 intel x86 汇编。我使用了这个简单的 C 代码。

#include <stdio.h> 
void function(){
 char c[1];
}
int main(){
 function();
 return 0;
}

我用下面的命令编译得到汇编。

gcc -S -o code.s code.c

函数的预期输出程序集如下:

pushl %ebp
movl %esp, %ebp
subl , %esp

我的理解是 1 个字符是 1 个字节,在英特尔 x86 中 1 个字的大小是 4 个字节,因为我们按字大小进行分配,所以应该在堆栈上分配总共 4 个字节的内存长度为 1 的 char 数组。但是为什么上面的程序集显示 24 个字节的分配。

经过反复试验,我发现如果 char 数组长度保持在 1-12 范围内,则汇编代码显示 24 个字节的分配,但如果超过 12 个,假设它是 13,则显示 40 个字节。

让我很困惑..

用于 IA-32 (Intel386) 的原始 System V Unix ABI 要求堆栈指针在 4 字节边界上对齐。1(未发布)Linux IA-32 的 ABI 基于原始的 System V Unix ABI,但在 2015 年 2 月发布了更新的 Linux IA-32 ABI 版本 1.0,要求堆栈指针对齐到 16(32 如果 __m256在调用函数时在堆栈上传递)字节边界。2(此ABI于2015年12月更新至1.1版,3据我所知,这是撰写本文时最新的。)

函数调用的编译器序言代码通常假设堆栈在入口处已经对齐。它假定 %esp+4+4 占堆栈上的 return 地址)对齐到 16(或 32)字节边界,并确保堆栈指针减少调用另一个函数时为 16 的倍数(必要时对齐 32 的倍数)。即使被调用的函数不调用另一个函数,它也会这样做。

对于下面的序言:

pushl %ebp
movl %esp, %ebp
subl , %esp

在第一条指令 (pushl %ebp) 之前,%esp+4 对齐到 16 字节边界。在第一条指令之后,%esp+4+4 与 16 字节边界对齐。在第三条指令(subl , %esp)之后,%esp+4+4+24 对齐到 16 字节边界,并且由于 4+4+24 是 16 的倍数,因此 %esp 对齐到 16 字节边界.

编译器可以subl , %esp替换subl , %esp并且仍然保持堆栈与16字节边界对齐。我不知道分配额外16个字节的原因。

Linux 的 GCC 编译器在发布修订版 ABI 的 1.0 版之前已经假设堆栈已经对齐到 16 字节边界多年。这导致 GCC 4.4 破坏了几个二进制文件。 “修复”是更改 ABI,破坏现有的旧代码,而不是修复编译器以与现有代码兼容。4


脚注:

  1. System V Application Binary Interface, Intel386™ Architecture Processor Supplement Fourth Edition — 图 3-15:标准栈帧。
  2. System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.0 — 2.2.2 栈帧。
  3. System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1
  4. Bug 40838 - gcc shouldn't assume that the stack is alignedComment 86 by H.J.Lu (2011-01-18 21:07:26 UTC):

    I am in the process of updating i386 psABI to specify 16byte stack alignment.