啊我摔倒了..有没有人扶我起来学习....
@TOC
前言
诸如此类的表达式(++i) + (++i) + (++i)
,很多学校都喜欢用在学生的期末考里,看似经典的考题,有没有可能本身就是错误的呢?这种错误并不是语法错误,是可以正常运行的,这就造成了“==它是正确的编程==”这种假象
一、为什么(++i) + (++i) + (++i)是错误编程?
因为(++i) + (++i) + (++i)
在不同编译器上有不同的结果,这取决于编译器本身对操作符优先级的实现方式不同。它在Linus环境下结果是10
,而在VS环境下却是12
。能造成这种模棱两可的结果,当然不能认为是正确的编程。
二、探究VS如何实现(++i) + (++i) + (++i)
因为每种编译器的实现方式不同,但都可以通过观察了解实现方式,这边用VS举个例子
1. 反汇编
- 相信看了博主的《C | 函数栈帧的创建和销毁》的铁汁都很了解这套流程啦,如果没看过的铁汁想更深入地了解可以点击跳转过去
- 废话不多说啦,咱们撸起袖子开干!
- 进入反汇编前,需要先点击键盘
F10
或F11
(目前先不作区分,都一样)进入调试,然后在空白处用鼠标右键点击,呼出菜单后点击==转到反汇编==即可
2. 分析指令
- 前面部分(红色方框内)我们直接略过,都不是这次的重点,想了解的铁汁同样可以去《C | 函数栈帧的创建和销毁》
- 为了方便观察地址的变化,我们把显示符号名去掉
- 开始分析与
(++i) + (++i) + (++i)
的计算相关的指令:
该行指令表示,把1
赋值给地址为ebp-8
的这个地方(其实就是i
所在),于是i = 1
。
注:ebp
一种存地址的寄存器,也成为栈底指针;dword为双字,占4个字节
- 此行命令表示,把
i
的值赋给寄存器eax
- 然后
eax
加1
,此时eax
中存着2
- 再把
eax
的值赋给i
,此时i = 2
,也就是i
完成了第一次自加
- 下面三步同理,使得
i
完成了第二次自加,此时i = 3
- 下面三步同理,使得
i
完成了第三次自加,此时i = 4
- 把
i
的值赋给寄存器eax
,此时eax = 4
- 把
i
的值加给寄存器eax
,此时eax = 8
- 把
i
的值加给寄存器eax
,此时eax = 12
- 最后把
eax
的值赋给地址为ebp-14h
的这个地方(其实就是ret
所在),此时ret = 12
(0x0000000c)
三、总结
- 可以发现,VS是先把三个括号里的
i++
自加完,再将三个i
进行相加的,即4 + 4 + 4 = 12
- 不难猜出,linus环境下应该是先算前两个
i++
,此时i = 3
,然后先加起来3 + 3
存放在寄存器中,接着才算第三个i++
,此时i = 4
(此时的i
虽然变了,但是无法影响寄存器里的3 + 3
),再把此时的i
加在寄存器中,即最终为3 + 3 + 4 = 10
,然后寄存器再把10
赋值给ret
。 - 从中我们可以知道,并不是代码越复杂就显得我们越厉害,就连编译器都读不懂的代码我们又要来何用呢?高效且可读性强的代码才是我们的追求!希望通过本文可以让铁汁们省去不必要的时间去钻研这些鬼东西~