我做了一个函数来计算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上测试时),但这是必要的吗?
不,至少在理论上,堆栈框架并不总是必需的。优化编译器在某些情况下可能会避免使用调用堆栈。特别是当它能够内联被调用的函数时(在某些特定的调用站点中),或者当编译器成功检测到尾调用时(它重用调用者的框架)。
阅读平台的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标准没有提到堆栈。它让编译器实现可以自由选择他们认为最好的评估方法。