自增运算符的副作用

原文地址:http://www.titilima.cn/show-234-1.html

 

结论:

vs2008  一、先计算完所有的++i 将i的结果放入i中 二、计算机表达式的值 三、再计算所有的i++

G++也是先忽略i++,不过和vs2008区别之处在于不是先计算完所有的++i,再计算机表达式,而只是对每一个运算符先计算++i

 

好了,就从最为臭名昭著的“(++i) + (++i) + (++i)”开始吧。

C++代码
  1. int  i = 4;   
  2. int  a = (++i) + (++i) + (++i);  

题目要求是求a的值,多见于各种等级考试、期末考试的选择题。
显然,这道题的考点是前缀自增运算符。与之相似的还有后缀自增(减)或前后缀增减混合的情况。
一墙之隔,围城内外。在象牙塔外的世界,这个题目是最早遭到诟病者之一。因为,出题者所默认的程序运行环境是Turbo C,所以标准答案自然也就是TC的运行结果(有TC的朋友们不妨试一试,看看TC的结果是不是你那卷子上的标准答案)。而事实上,对于这个题目的结果,a 的值是无法预期的——C/C++标准规定,三个++i的子表达式是没有求值顺序点的,同时它们又是有副作用的,因此语言本身并不能保证副作用的顺序。
眼见为实,让我们来看看三款不同编译器产生的代码吧。我为每个必要的细节加了注释,以便理解。

汇编代码
  1. ; Visual C++ 6.0 sp6:   
  2. mov [ebp+i], 4   ; i = 4   
  3. mov eax, [ebp+i]   
  4. add eax, 1       ; ++i, i == 5   
  5. mov [ebp+i], eax   
  6. mov ecx, [ebp+i]   
  7. add ecx, 1       ; ++i, i == 6   
  8. mov [ebp+i], ecx   
  9. mov edx, [ebp+i]   
  10. add edx, [ebp+i] ; 6 + 6 == 12   
  11. mov eax, [ebp+i]   
  12. add eax, 1       ; ++i, i == 7   
  13. mov [ebp+i], eax   
  14. add edx, [ebp+i] ; 12 + 7 == 19   
  15. mov [ebp+a], edx ; a = 19   
  16.   
  17. ; Visual Studio 2005:   
  18. mov [ebp+i], 4   ; i = 4   
  19. mov eax, [ebp+i]   
  20. add eax, 1       ; ++i, i == 5   
  21. mov [ebp+i], eax   
  22. mov ecx, [ebp+i]   
  23. add ecx, 1       ; ++i, i == 6   
  24. mov [ebp+i], ecx   
  25. mov edx, [ebp+i]   
  26. add edx, 1       ; ++i, i == 7   
  27. mov [ebp+i], edx   
  28. mov eax, [ebp+i]   
  29. add eax, [ebp+i] ; 7 + 7 == 14   
  30. add eax, [ebp+i] ; 14 + 7 == 21   
  31. mov [ebp+a], eax ; a = 21   
  32.   
  33. ; gcc.exe (GCC) 3.4.5 (mingw-vista special):   
  34. mov [ebp+i], 4      ; i = 4   
  35. lea eax, [ebp+i]   
  36. inc dword ptr [eax] ; ++i, i == 5   
  37. lea eax, [ebp+i]   
  38. inc dword ptr [eax] ; ++i, i == 6   
  39. mov eax, [ebp+i]   
  40. mov edx, [ebp+i]   
  41. add edx, eax        ; 6 + 6 == 12   
  42. lea eax, [ebp+i]   
  43. inc dword ptr [eax] ; ++i, i == 7   
  44. mov eax, edx   
  45. add eax, [ebp+i]    ; 12 + 7 == 19   
  46. mov [ebp+a], eax    ; a = 19  

——其实我大可不必列出如是这般冗长的汇编代码,而只需要一个a值结果的总结表格就可以说明问题。不过我还是选择了汇编语言,原因有二:第一,任何 的砖家、叫兽告诉你的东西都远远不及最终生成的目标代码可靠;第二,使用汇编代码可以把自己伪装成高手,用来装B的效果肯定比简单的表格来得有效,何乐而 不为哉。
装都装了,自然不怕遭雷劈。再来一个嵌入式设备上的代码,环境是eMbedded Visual C++ 4.0 sp4的ARMV4编译器:

汇编代码
  1. MOV R0, #4        ; i = 4   
  2. STR R0, [SP,#8+i]   
  3. LDR R1, [SP,#8+i]   
  4. ADD R0, R1, #1    ; ++i, i == 5   
  5. STR R0, [SP,#8+i]   
  6. LDR R1, [SP,#8+i]   
  7. ADD R0, R1, #1    ; ++i, i == 6   
  8. STR R0, [SP,#8+i]   
  9. LDR R1, [SP,#8+i]   
  10. ADD R0, R1, #1    ; ++i, i == 7   
  11. STR R0, [SP,#8+i]   
  12. LDR R1, [SP,#8+i]   
  13. LDR R0, [SP,#8+i]   
  14. ADD R2, R1, R0    ; 7 + 7 == 14   
  15. LDR R3, [SP,#8+i]   
  16. ADD R0, R2, R3    ; 14 + 7 == 21   
  17. STR R0, [SP,#8+a] ; a = 21  

相信到这里诸位都看到了,一个表达式在不同的编译器上会出现不同的结果——特别是微软的VC6和VS2005,一家产的编译器的结果也是不一样的。 亦即是说,倘使你写下了诸如“(++i) + (++i) + (++i)”这样的代码,你得到的结果将是一个无法预期的结果,必须的。
末了,说点八卦的。很多程序员将这种病态、晦涩的编码方式归咎于谭浩强版的《C程序设计》,认为谭老爷子是这种学究代码的始作俑者。李马饶有兴致地考证了一番,发现谭老爷子在《C程序设计》(第二版)的第58~59页中对这种情况进行了讨论,并指出以下几点:

  1. 应该避免++/--的副作用可能产生的歧义性,建议将这样的表达式拆开写。
  2. 对于i+++j的情况,应使用括号来使代码明晰以避免误解,如(i++)+j或i+(++j)。
  3. “总之,不要写出别人看不懂的、也不知道系统会怎样执行的程序。”

窃为谭老爷子鸣不平啊。

掌掴学究欢迎提供各类素材,请致信titilima@163.com

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值