似曾相识 不过是个Bug...

大狗的窝~

<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固有的计算过程刚好一致。

 

总结:这样的题目只可能出现在咱们天朝中国风的试题之中,它将晦涩的语义体现的淋漓尽致,但是在编程实践中,这种将针对同一个变量的多个自增减运算,放在同一个表达式中的做法是被严格禁止的。告诉自己,请不要书写这样风格代码!表纠结!

 

 

 

 

posted on 2012-07-30 01:45  Mr.DejaVu  阅读(254)  评论(0编辑  收藏  举报

导航

for myself...