2

我无法理解为什么编译器选择以我编写的代码的方式偏移堆栈空间。

我正在玩弄 Godbolt 的 Compiler Explorer 以研究 C 调用约定,这时我想出了一个简单的代码,它的选择让我感到困惑。

代码可在此链接中找到。我选择了 GCC 8.2 x86-64,但我的目标是 x86 处理器,这很重要。Bellow 是编译器资源管理器报告的 C 代码和生成的程序集的转录。

// C code
int testing(char a, int b, char c) {
    return 42;
}

int main() {
    int x = testing('0', 0, '7');

    return 0;
}
; Generated assembly
testing(char, int, char):
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     edx, DWORD PTR [ebp+8]
        mov     eax, DWORD PTR [ebp+16]
        mov     BYTE PTR [ebp-4], dl
        mov     BYTE PTR [ebp-8], al
        mov     eax, 42
        leave
        ret
main:
        push    ebp
        mov     ebp, esp
        sub     esp, 16
        push    55
        push    0
        push    48
        call    testing(char, int, char)
        add     esp, 12
        mov     DWORD PTR [ebp-4], eax
        mov     eax, 0
        leave
        ret

从现在开始看汇编列,据我了解,第15行负责为局部变量保留堆栈中的空间。问题是我只有一个本地int的,偏移量是 16 个字节而不是 4 个。这感觉像是在浪费空间。

这是否与单词对齐有关?但即使是这样,如果通用寄存器的大小是 4 个字节,那么这种对齐不应该是 4 个字节吗?

我看到的另一件奇怪的事情是关于函数的 local chars 的位置。testing它们似乎在堆栈中每个占用 4 个字节,如第 7-8 行所示,但只有较低的字节被操作。为什么不每个只使用 1 个字节?

这些选择可能是出于好意,我真的很想了解它们的目的(或者是否没有目的)。或者,也许我只是感到困惑,并没有完全明白。

4

2 回答 2

2

因此,通过评论,我可以确定堆栈增长问题是由于 i386 SystemV ABI 要求造成的,正如@PeterCordes 所述。

字符对齐的原因可能是由于 GCC 提高速度的默认行为,这可能是从 @Ped7g 的评论中推断出来的。虽然不确定,但这对我来说是一个足够好的答案。

于 2019-01-16T06:10:01.107 回答
0

如今,以这种大小的倍数获取堆栈空间是很常见的,原因如下:

  • 缓存行通过在缓存中维护整个数据来支持这种行为。
  • 临时空间是预先分配的,避免在 cpu 需要一些临时存储时使用 push 和 pop 指令。
  • 单独的 push 和 pop 指令要求在执行下一条指令之前更新数据,从而降低了流水线的执行。这解耦了连续指令之间的数据依赖关系,让它们运行得更快。

出于这个原因,实际的编译器指定要以这种方式设计的 ABI。

于 2019-01-21T08:41:57.807 回答