今天被问到如下代码的输出是多少:
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
int a = 0;
int b = (++a) + (++a) + (++a);
cout<<b;
return 0;
}
编译了一下,发现 LLVM 和 GNU 输出的结果是不一样的:LLVM 是 6,GNU 是 7。很好奇为什么会这样,就看了一下汇编代码。
以下是 LLVM 的汇编结果:
Ltmp4:
.cfi_def_cfa_register %rbp
subq $32, %rsp
movq __ZNSt3__14coutE@GOTPCREL(%rip), %rax
movl $0, -4(%rbp)
movl %edi, -8(%rbp)
movq %rsi, -16(%rbp)
movl $0, -20(%rbp)
movl -20(%rbp), %edi
addl $1, %edi
movl %edi, -20(%rbp)
movl -20(%rbp), %ecx
addl $1, %ecx
movl %ecx, -20(%rbp)
addl %ecx, %edi
movl -20(%rbp), %ecx
addl $1, %ecx
movl %ecx, -20(%rbp)
addl %ecx, %edi
movl %edi, -24(%rbp)
movl -24(%rbp), %esi
movq %rax, %rdi
callq __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi
movl $0, %ecx
movq %rax, -32(%rbp) ## 8-byte Spill
movl %ecx, %eax
addq $32, %rsp
popq %rbp
retq
.cfi_endproc
相比 LLVM 给出的汇编代码,GNU 给出的编译代码会长很多,这里只给出相关的部分。
LCFI1:
subq $32, %rsp
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0, -4(%rbp)
addl $1, -4(%rbp)
addl $1, -4(%rbp)
movl -4(%rbp), %eax
leal (%rax,%rax), %edx
addl $1, -4(%rbp)
movl -4(%rbp), %eax
addl %edx, %eax
movl %eax, -8(%rbp)
movl -8(%rbp), %eax
movl %eax, %esi
movq __ZSt4cout@GOTPCREL(%rip), %rax
movq %rax, %rdi
call __ZNSolsEi
movl $0, %eax
leave
阅读上述关键代码的可以看到,LLVM 是将结果逐个的存储到 edi 寄存器中,而 GNU 会先把两个 (++a) 计算出来,并保存在 a 中,此时,a 已经为 2 了,然后两者相加,结果再和后面的变量相加,因此,在第一次相加的时候多加了一次。
这样的代码估计除了面试、笔试之外,大概也不会有人写出来生产使用吧。
PS:
- GNU 编译结果中的 lea 指令不太清楚具体是什么含义,要再深入了解一下。
- 栈竟然真的是向下增长的,我还以为他们在胡说。
- EOF -