有两个简单的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;
}
另一个是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;
}
我得到了两者的可重定位目标文件。我发现gcc在main
中对函数mainswap.c
的堆栈帧进行了对齐,同时gcc没有为main
中的函数mainfoobar.c
明确执行。< / p>
mainswap.c的主要内容:
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的主要内容:
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个字节?
答案 0 :(得分:1)
您似乎在未启用优化的情况下编译了这两个。这解释了您所看到的一些行为,因为在该模式下,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放宽了对齐,因为它可以证明更严格的对齐是不必要的。
通过快速谷歌搜索,I found this link讨论了一些问题并提供了一些其他指示。是的,这是关于FreeBASIC的错误报告,但它直接触及了这个问题。