Yi's Blog

目之所及,尽是萌芽

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

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

#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 -