自增运算符的副作用

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

结论:

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

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

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

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

汇编代码
; Visual C++ 6.0 sp6:   
mov [ebp+i], 4   ; i = 4   
mov eax, [ebp+i]   
add eax, 1       ; ++i, i == 5   
mov [ebp+i], eax   
mov ecx, [ebp+i]   
add ecx, 1       ; ++i, i == 6   
mov [ebp+i], ecx   
mov edx, [ebp+i]   
add edx, [ebp+i] ; 6 + 6 == 12   
mov eax, [ebp+i]   
add eax, 1       ; ++i, i == 7   
mov [ebp+i], eax   
add edx, [ebp+i] ; 12 + 7 == 19   
mov [ebp+a], edx ; a = 19   
  
; Visual Studio 2005:   
mov [ebp+i], 4   ; i = 4   
mov eax, [ebp+i]   
add eax, 1       ; ++i, i == 5   
mov [ebp+i], eax   
mov ecx, [ebp+i]   
add ecx, 1       ; ++i, i == 6   
mov [ebp+i], ecx   
mov edx, [ebp+i]   
add edx, 1       ; ++i, i == 7   
mov [ebp+i], edx   
mov eax, [ebp+i]   
add eax, [ebp+i] ; 7 + 7 == 14   
add eax, [ebp+i] ; 14 + 7 == 21   
mov [ebp+a], eax ; a = 21   
  
; gcc.exe (GCC) 3.4.5 (mingw-vista special):   
mov [ebp+i], 4      ; i = 4   
lea eax, [ebp+i]   
inc dword ptr [eax] ; ++i, i == 5   
lea eax, [ebp+i]   
inc dword ptr [eax] ; ++i, i == 6   
mov eax, [ebp+i]   
mov edx, [ebp+i]   
add edx, eax        ; 6 + 6 == 12   
lea eax, [ebp+i]   
inc dword ptr [eax] ; ++i, i == 7   
mov eax, edx   
add eax, [ebp+i]    ; 12 + 7 == 19   
mov [ebp+a], eax    ; a = 19  
——其实我大可不必列出如是这般冗长的汇编代码,而只需要一个a值结果的总结表格就可以说明问题。不过我还是选择了汇编语言,原因有二:第一,任何的砖家、叫兽告诉你的东西都远远不及最终生成的目标代码可靠;第二,使用汇编代码可以把自己伪装成高手,用来装B的效果肯定比简单的表格来得有效,何乐而不为哉。
装都装了,自然不怕遭雷劈。再来一个嵌入式设备上的代码,环境是eMbedded Visual C++ 4.0 sp4的ARMV4编译器:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值