什么决定gcc是否为对齐做了一些事情?;
有两个简单的C源文件。第一个是mainswap.c:什么决定gcc是否为对齐做了一些事情?;,gcc,memory-alignment,stack-frame,Gcc,Memory Alignment,Stack Frame,有两个简单的C源文件。第一个是mainswap.c: void swap(int *x, int *y); int main() { int a, b; a = 5; b = 44; swap(&a, &b); return 0; } void swap(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } main: push
void swap(int *x, int *y);
int main()
{
int a, b;
a = 5;
b = 44;
swap(&a, &b);
return 0;
}
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $5, 24(%esp)
movl $44, 28(%esp)
leal 28(%esp), %eax
movl %eax, 4(%esp)
leal 24(%esp), %eax
movl %eax, (%esp)
call swap
movl $0, %eax
leave
ret
另一个是mainfoobar.c:
int bar(int x, int y)
{
int z = x + y;
return z;
}
int foo(int a, int b)
{
return bar(a, b);
}
int main(void)
{
foo(2, 3);
return 0;
}
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $3, 4(%esp)
movl $2, (%esp)
call foo
movl $0, %eax
leave
ret
我得到了两者的可重定位对象文件。我发现gcc在mainswap.c
中对函数main
的堆栈框架进行了对齐,而在mainfoobar.c
中,gcc并没有对函数main
进行显式对齐
mainswap.c的主要功能:
void swap(int *x, int *y);
int main()
{
int a, b;
a = 5;
b = 44;
swap(&a, &b);
return 0;
}
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $5, 24(%esp)
movl $44, 28(%esp)
leal 28(%esp), %eax
movl %eax, 4(%esp)
leal 24(%esp), %eax
movl %eax, (%esp)
call swap
movl $0, %eax
leave
ret
mainfoobar.c的主要功能:
int bar(int x, int y)
{
int z = x + y;
return z;
}
int foo(int a, int b)
{
return bar(a, b);
}
int main(void)
{
foo(2, 3);
return 0;
}
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $3, 4(%esp)
movl $2, (%esp)
call foo
movl $0, %eax
leave
ret
我知道,andl$-16、%esp
和subl$32、%esp
的意图。相同的gcc版本,相同的选项,相同的计算机,唯一的区别是C源文件。我的问题是为什么gcc对这两个主要功能有不同的看法,这一现象的背后是什么
此外,我使用的gcc版本为:
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright © 2011 Free Software Foundation, Inc.
顺便说一下,在mainswap.c的函数
main
中,我认为subl$16,%esp
也将满足对齐和使用需求,为什么gcc会浪费16个字节?似乎您在未启用优化的情况下编译了这两个字节。这就解释了您所看到的一些行为,因为在这种模式下,GCC通常会自上而下地处理文件,而不会尝试跨函数内联或优化。但是,当它到达一个函数时,它将对它所知道的一切起作用
在您的两个程序中,main
显示在它调用的函数之前或之后。这似乎是这里的关键
如果将mainfoobar.c更改为如下所示:
int main(void)
{
foo(2, 3);
return 0;
}
int bar(int x, int y)
{
int z = x + y;
return z;
}
int foo(int a, int b)
{
return bar(a, b);
}
您将在两个版本的输出中看到类似的堆栈对齐操作。我怀疑两者之间的区别在于编译器是否在代码生成过程中到达main
时看到了被调用方。当main
排在第一位时,它生成的堆栈帧比main
排在所有被调用方之后时更保守
显然,随着SSE的出现,GCC的32位x86 ABI现在需要跨函数调用边界的16字节对齐,除非它能证明不是这样。我不知道这有多严格。并且,正如上面的示例所示,当GCC可以证明不需要更严格的对齐时,它会放松对齐
通过快速的谷歌搜索,这讨论了一些问题,并给出了一些其他的提示。是的,这是一个关于FreeBASIC的错误报告,但它直接涉及到这个问题。你用什么标志编译这个?FWIW,我用稍旧的Ubuntu GCC版本进行了本地测试,
GCC版本4.4.5(Ubuntu/Linaro 4.4.4-14ubuntu5.1)
,但我的结果似乎与你的一致。根据FreeBASIC线程,16字节首选堆栈对齐显示在GCC4.1左右。正如我上面所说,我不确定这有多严格。不过,FreeBASIC线程读起来相当有趣。