GCC 和 Clang 都可以编译
bool pred();
void f();
void g();
void h() {
if (pred()) {
f();
} else {
g();
}
}
的一些变体
# Clang -Os output. -O3 is the same
h():
push rax
call pred()@PLT
test al, al
je .LBB0_2
pop rax
jmp f()@PLT
.LBB0_2:
pop rax
jmp g()@PLT
优化代码大小时。您可以在编译器资源管理器上看到这一点。
是否有理由在两个分支上发出
pop
指令,而不是在 je
之前只发出一次?
例如,弹出到不同的调用破坏寄存器以避免破坏 pred() 返回值,或使用
add rsp, 8
(在这种情况下,在现代 CPU 上实际上可能并不更快,因为它需要堆栈同步 uop。)
# hand-written example of what compilers could be doing
h():
push rax # align the stack
call pred()@PLT
pop rcx # clean up the stack, restoring the stack pointer to its initial state
test al, al
je .LBB0_2
jmp f()@PLT # tailcall f
.LBB0_2:
jmp g()@PLT # tailcall g
它是高级结构和通过跳转优化尾部调用的尝试的混合的一部分。每当一个函数以调用另一个函数(或返回)结束时,编译器就会使用
pop rax; jmp function()
代码。
在你的情况下,两个分支都是尾部调用。为了简洁起见,我使用三元,它生成与
if()
: 相同的代码
bool pred();
void f();
void g();
void i();
void h() {
pred() ? f() : g();
i();
}
// asm
h():
push rdx
call pred()
test al, al
je .L2
call f()
jmp .L3
.L2:
call g()
.L3:
pop rax
jmp i()
看起来和预期的一样,对吧?如果我们按顺序调用三个,尾部就会弹出:
void h() {
pred();
f();
g();
}
h():
push rax
call pred()
call f()
pop rdx
jmp g()