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

时间:2017-07-24 15:28:49

标签: c assembly x86 stack x86-64

我已经编写了一个函数来计算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 个答案:

答案 0 :(得分:4)

不,至少在理论上,并不总是需要堆栈帧。在某些情况下,优化编译器可能会避免使用call stack。值得注意的是,当它能够inline一个被调用的函数(在某个特定的调用站点中),或者当编译器成功检测到tail call(它重用了调用者的帧)时。

阅读平台的ABI以了解与堆栈相关的要求。

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

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

BTW,我刚刚在long fact(int n)(即-O3)用GCC 7.1(在Debian / Sid / x86-64上)编译了一个朴素的递归gcc -fverbose-asm -S -O3 fact.c阶乘函数。生成的汇编代码fact.s包含 no call机器指令。

答案 1 :(得分:1)

  

每个C函数都使用...

设置堆栈帧

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

ISO 9899标准没有说明堆栈。它使编译器实现可以自由选择他们认为最好的评估方法。