是什么决定了gcc是否能够做对齐?

时间:2013-07-02 02:05:33

标签: gcc memory-alignment stackframe

有两个简单的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, %espsubl $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个字节?

1 个答案:

答案 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的错误报告,但它直接触及了这个问题。