在执行 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
脚注:
- System V Application Binary Interface, Intel386™ Architecture Processor Supplement Fourth Edition — 图 3-15:标准栈帧。
- System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.0 — 2.2.2 栈帧。
- System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1
- Bug 40838 - gcc shouldn't assume that the stack is aligned — Comment 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.
我正在学习 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
脚注:
- System V Application Binary Interface, Intel386™ Architecture Processor Supplement Fourth Edition — 图 3-15:标准栈帧。
- System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.0 — 2.2.2 栈帧。
- System V Application Binary Interface Intel386 Architecture Processor Supplement Version 1.1
- Bug 40838 - gcc shouldn't assume that the stack is aligned — Comment 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.