提问者:小点点

x86-64上C中的所有函数都需要堆栈帧吗?


我做了一个函数来计算C字符串的长度(我试图使用-O3击败clang的优化器)。我正在运行macOS。

_string_length1:
    push rbp
    mov rbp, rsp
    xor rax, rax
.body:
    cmp byte [rdi], 0
    je .exit
    inc rdi
    inc rax
    jmp .body
.exit:
    pop rbp
    ret

这是我试图击败的C函数:

size_t string_length2(const char *str) {
  size_t ret = 0;
  while (str[ret]) {
    ret++;
  }
  return ret;
}

它分解成这样:

string_length2:
    push rbp
    mov rbp, rsp
    mov rax, -1
LBB0_1:
    cmp byte ptr [rdi + rax + 1], 0
    lea rax, [rax + 1]
    jne LBB0_1
    pop rbp
    ret

每个C函数都使用push rbpmov rbp, rsp设置堆栈帧,并使用pop rbp对其进行破坏。但我在这里没有以任何方式使用堆栈,我只使用处理器寄存器。它在不使用堆栈帧的情况下工作(当我在x86-64上测试时),但这是必要的吗?


共2个答案

匿名用户

不,至少在理论上,堆栈框架并不总是必需的。优化编译器在某些情况下可能会避免使用调用堆栈。特别是当它能够内联被调用的函数时(在某些特定的调用站点中),或者当编译器成功检测到尾调用时(它重用调用者的框架)。

阅读平台的ABI,了解与堆栈相关的需求。

您可以尝试使用链接时间优化编译您的程序(例如使用gcc-flto-O2编译和链接)以获得更多优化。

原则上,可以想象一个足够聪明的编译器(对于某些程序)避免使用任何调用堆栈。

BTW,我刚刚在-O3(即gcc-fverose-asm-S-O3 fact. c)编译了一个GCC7.1(在Debian/Sid/x86-64上)的简单递归long事实(int n)阶乘函数。生成的汇编代码事实.s不包含调用机器指令。

匿名用户

每个C函数都使用…

对于您的编译器来说是如此,而不是一般情况下。完全不使用堆栈就可以编译C程序——例如,参见方法CPS,延续传递样式。市场上可能没有C编译器这样做,但重要的是要知道除了堆栈评估之外,还有其他执行程序的方法。

ISO9899标准没有提到堆栈。它让编译器实现可以自由选择他们认为最好的评估方法。