我已经编写了一个函数来计算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 rbp
和mov rbp, rsp
设置堆栈帧,并使用pop rbp
将其分解。但我在这里没有以任何方式使用堆栈,我只使用处理器寄存器。它在不使用堆栈框架的情况下工作(当我在x86-64上测试时),但它是否有必要?
答案 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标准没有说明堆栈。它使编译器实现可以自由选择他们认为最好的评估方法。