0

有两个简单的 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 对 in 函数的堆栈帧的对齐做了一些事情mainmainswap.c同时 gcc 并没有明确地为函数mainin做一些事情mainfoobar.c

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, %espand的意图subl $32, %esp。相同的 gcc 版本,相同的选项,相同的计算机,唯一的区别是 C 源文件。我的问题是为什么 gcc 对两个主要函数的处理方式不同,这种现象的背后是什么?</p>

另外,我使用的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个字节?</p>

4

1 回答 1

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 可以证明不需要更严格的对齐时,它会放宽对齐。

通过快速谷歌搜索,我发现这个链接讨论了一些问题并提供了一些其他指示。是的,这是关于 FreeBASIC 的错误报告,但它直接涉及到这个问题。

于 2013-07-02T02:27:02.330 回答