<C/C++> 关于自增运算符的一段纠结
事出一道题:
1 int a=5; 2 printf("%d\n",a++ + ++a);
输出为12,之后a为7.
木有悬念. 第一个a后++为5;第二个a前++为6;计算表达式的值,此时操作数的值从a取,故6+6=12,输出;最后完成第一个a的后++,a为7.
然后呢..我就想到
1 int a=5; 2 printf("%d\n",a++ + ++a + a++); //(1) 3 a=5; 4 printf("%d\n",a++ + ++a + ++a); //(2)
会是神马呢?
按我的惯性理解:(1)为18无悬念,(2)呢? 21吧...先完成后2个a的前++,a值为7;计算表达式的值7+7+7=21;最后再做一个后++,a为8.
但gcc的编译结果:19!
不服气的我在纸上比划了好半天...乃个纠结啊..然后去VS2005跑了一圈,咦? VS编译结果却是21.
我首先感觉是坦然(小样!哥哥也没错,都是编译器惹得祸^^).
但是接着我又是继续纠结想不通,发了封邮件给老师.闵老师很负责给回答了,如下:
以a++ + ++a + ++a为例,在gcc中的处理步骤是:
S1: 计算a++表达式的值,因其为后++,故a的值不变,仍为5;
S2: 计算左数第一个++a表达式的值,因其为前++,故a的值变为6;
S3: 计算上面两个表达式的和,注意自增减表达式的值直接从操作数a中获取,不使用临时变量,而此时a的值已经是6,6+6=12,故a++ + ++a表达式的值为12,这个12作为一个中间结果会被保存在一个临时变量或CPU寄存器里;
S4: 计算左数第二个++a表达式的值,因其为前++,故a的值变为7;
S5: 将临时变量或CPU寄存器中保存的中间结果与S4所得结果相加,12+7=19,整个表达式的值为19;
S6: 因为表达式中有一个后++,所以需要对a再加一次,a的值变为8。
另一些编译器的处理会略有不同,它们可能会先将三个子表达式(a++、++a、++a)先计算一遍,此时a中的值为7,再对这三个子表达式的计算结果求和,同样因为自增减表达式不使用临时变量而是直接从操作数a中取值,因此实际参与计算的是7+7+7=21。
关键的区别在于,gcc是先将a++ + ++a算完,拿这个中间结果去和最后那个++a相加;而vc是把a++、++a、++a都算完以后,再计算整个表达式的值。
为此我对比了gcc和VS编译产生的汇编代码:
gcc:
1 movl $5, -4(%ebp) 2 leal -4(%ebp), %eax 3 incl (%eax) ;++a,a为6 4 movl -4(%ebp), %eax 5 movl %eax, %edx 6 addl -4(%ebp), %edx ;a++ + ++a中间变量12保存进edx 7 leal -4(%ebp), %eax 8 incl (%eax) ;++a,a为7 <===== 9 movl %edx, %eax 10 addl -4(%ebp), %eax ;12+7=19 11 movl %eax, 4(%esp) 12 leal -4(%ebp), %eax 13 incl (%eax) 14 movl $LC0, (%esp) 15 call _printf
VS2005:
1 a=5; 2 00411450 mov dword ptr [a],5 3 printf("%d\n",a++ + ++a + ++a); 4 00411457 mov eax,dword ptr [a] 5 0041145A add eax,1 ;++a,a为6 6 0041145D mov dword ptr [a],eax 7 00411460 mov ecx,dword ptr [a] 8 00411463 add ecx,1 ;++a,a为7 <===== 9 00411466 mov dword ptr [a],ecx 10 00411469 mov edx,dword ptr [a] ;edx=7 11 0041146C add edx,dword ptr [a] ;edx+a edx为14 12 0041146F add edx,dword ptr [a] ;edx+a edx为21 13 00411472 mov dword ptr [ebp-0D0h],edx 14 00411478 mov eax,dword ptr [a] 15 0041147B add eax,1 16 0041147E mov dword ptr [a],eax 17 00411481 mov esi,esp 18 00411483 mov ecx,dword ptr [ebp-0D0h] 19 00411489 push ecx 20 0041148A push offset string "%d\n" (41573Ch) 21 0041148F call dword ptr [__imp__printf (4182BCh)] 22 00411495 add esp,8 23 00411498 cmp esi,esp 24 0041149A call @ILT+320(__RTC_CheckEsp) (411145h)
同理,类似的情况还有:
1 int a=5; 2 printf("%d\n",++a + ++a + ++a); //(1) 3 a=5; 4 printf("%d\n",++a + (++a + ++a)); //(2)
(1)gcc输出22,VS2005输出24
(2)gcc和VS2005都输出24!
对于(2),小括号的加入改变了三个子表达式的结合顺序,即便是gcc也无法提前计算前两个++a子表达式的和,而必须等到最后一个++a计算完以后才能开始对整个表达式求和,而此时a的值已经加到8了。这与vc固有的计算过程刚好一致。
总结:这样的题目只可能出现在咱们天朝中国风的试题之中,它将晦涩的语义体现的淋漓尽致,但是在编程实践中,这种将针对同一个变量的多个自增减运算,放在同一个表达式中的做法是被严格禁止的。告诉自己,请不要书写这样风格代码!表纠结!