有这样一段代码:
int a = 2;
a = a++;
printf("%d\n", a);
在Visual Studio2012编译环境里,输出的结果是3。而用gcc编译得到2。
这是为什么?
先说GCC。出现2的原因是,gcc在做右边操作数为a++的赋值操作时,会将a的原值和++后的新值放在两个寄存器内,然后按先给a新值、后给左边操作数以原值的顺序来执行代码。用c语言来说,就是先++,再赋值。可以参照b=a++的编译结果:
movl $2, -8(%rbp) // a = 2
movl -8(%rbp), %eax // 原值2
leal 1(%rax), %edx // 新值3, a++
movl %edx, -8(%rbp) // a = 3 先赋新值给回a地址
movl %eax, -4(%rbp) // 再赋原值2给内存b地址
而在a=a++时,还按照这种顺序来就会出现奇怪(?)的现象。
movl $2, -4(%rbp) // a = 2
movl -4(%rbp), %eax // 原值
leal 1(%rax), %edx // 新值
movl %edx, -4(%rbp) // 先赋新值3
movl %eax, -4(%rbp) // 再赋原值2,又改回去了
而VS编译器的做法也应该能猜到了,先给回原值,再给新的值。
mov eax, DWORD PTR _a$[ebp] ;a=-8
mov DWORD PTR _a$[ebp], eax ;先给原值
mov ecx, DWORD PTR _a$[ebp]
add ecx, 1
mov DWORD PTR _a$[ebp], ecx ;再给新值
但是,千万别觉得VS比GCC高到哪里去。原因是这条语句本身就不被c标准支持,所以各个厂家按照自己编译器标准来,以至于出来两种截然不同的“标准”。毕竟,写c程序也要按照基本法啊。
后记:出于好奇,我在ubuntu14.04下用最新的arm-linux-gnueabi-gcc交叉编译了一下同样的程序:
#include <stdio.h>
int main()
{
int a = 2;
int b = 3;
a = a ++;
return 0;
}
arm汇编代码如下:
mov r3, #2
str r3, [fp, #-12]
mov r3, #3
str r3, [fp, #-8]
ldr r3, [fp, #-12]
add r3, r3, #1
str r3, [fp, #-12]
mov r3, #0
mov r0, r3
全程只用一个寄存器r3,并且直接在加完1后就赋值回去了!
对比一下b=a++的代码,明显可以看到其中的奥妙:编译器直接忽略了a=a的两句指令。
mov r3, #2
str r3, [fp, #-12] ; a = 2
mov r3, #3
str r3, [fp, #-8] ; b = 3
ldr r3, [fp, #-12]
str r3, [fp, #-8] ; b = a
ldr r3, [fp, #-12]
add r3, r3, #1
str r3, [fp, #-12] ; a ++
mov r3, #0