原文地址:http://www.titilima.cn/show-234-1.html
结论:
vs2008 一、先计算完所有的++i 将i的结果放入i中 二、计算机表达式的值 三、再计算所有的i++
G++也是先忽略i++,不过和vs2008区别之处在于不是先计算完所有的++i,再计算机表达式,而只是对每一个运算符先计算++i
好了,就从最为臭名昭著的“(++i) + (++i) + (++i)”开始吧。
- 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)。
- “总之,不要写出别人看不懂的、也不知道系统会怎样执行的程序。”
窃为谭老爷子鸣不平啊。
掌掴学究欢迎提供各类素材,请致信titilima@163.com 。