Jeswang's Blog

盲目跟随还是独立去做,To be or not to be?

(++a)+(++a)+(++a) 的计算结果

| Comments

今天被问到如下代码的输出是多少:

1
2
3
4
5
6
7
8
#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 的汇编结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 给出的编译代码会长很多,这里只给出相关的部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 -

Comments