[音乐] 前几讲已经介绍了IA-32的传送指令、
定点算术运算指令、 位操作指令 本讲接着介绍控制转移指令,同样这里的所以
细节内容不需要记忆。
IA-32当中的 常用指令类型我们前面介绍了传送指令、
定点的算术运算指令、 位操作指令三类 那么我们今天介绍第四类控制转移指令
我们知道指令的执行它有两种顺序,一种是按顺序
执行,还有一种呢是要跳转到一个转移目标指令处执行
对于无条件转移指令它执行到这条指令的时候
那么这条指令执行完了以后就直接跳转到一个转移目标指令的地方去执行
它是一种无条件转移的,比如说jump指令,这个jump指令就是无条件地转移到
DST所指出的那个目标指令处执行 条件转移指令是分支指令,它有可能是
按顺序执行,有可能要跳转到转移目标指令处执行
是跳转还是顺序执行主要是看条件是否满足
所以在这个指令当中有一些是表示条件的
助记符,cc这边就是一个条件码,它是根据标志
也就是条件标志来判断是否满足条件 如果满足条件就转移到目标指令处执行,DST指出
转移到的那个目标指令的地址,否则如果不满足条件的话就是按顺序执行
还有一类指令是条件设置指令 跟上面的这个条件转移指令类似的
在指令助记符当中有条件码,这个是说如果满足这个条件的话
就把这个目标寄存器,这是一个寄存器,里面置1,否则呢是0
这个是按条件码来判断这个结果是否满足 条件,如果满足条件的话就把相应的目标寄存器置1
还有一类指令也是要跳转的,主要是调用 和返回指令,就是在函数调用或者过程调用的时候
我们从一个过程或者叫一个函数跳转到另外一个过程或者函数去执行
这个跳转的指令专门有一种叫调用指令
从被调用过程当中返回到调用过程的时候我们可以通过专门的返回指令执行
那么这个调用指令就是CALL call指令,返回指令就是return指令RET
子程序调用或者是过程调用的时候,在被调用过程里面执行完了以后它还要回到调用过程执行
这个回来的这个地址必须保存在栈里面,这样的话执行到return指令的时候
可以从栈里面取出返回地址,根据这个返回地址转到返回点继续执行 中断指令它也是一种跳转指令
它的跳转实际上是从用户态跳转到了内核态 详细的信息在后面的章节中介绍
在这些条件转移或者条件设置指令 当中有一个非常重要的寄存器就是标志寄存器
在标志寄存器里面有几个常用的条件标志 我们前面已经讲过了,OF、
SF、 ZF和CF,这些标志信息 实际上是用来进行判断条件是否满足的一些信息
那么这些信息我们前面已经介绍过了,这些信息实际上是通过执行某一条指令生成的
这些指令在运算电路里面运算
运算的时候会得到相应的这种标志信息,比如说我们前面举过整数减法的这个例子
那么在做减法的时候,我们根据相应的这些运算结果
得到一组标志信息,这组标志信息可以用来判断大小 比如说我们比较大小的时候可以做减法
然后根据得到的标志来比较大小 比如说在无符号数比较的时候
我们是根据CF的值来确定是大于还是小于关系
CF在做减法的时候它表示借位
当有借位的时候,CF等于1的时候,很显然 它是小于,那么没有借位,也就是CF等于0的时候
它就是一种大于关系,这是无符号数,比如说我们刚才 前面的这个例子当中可以验证这边CF都是等于0
因此可以判断出9是大于6的,这是无符号数
这是无符号数的13和5比较,那么因为CF也是等于0
因此13应该是大于5,没有借位,所以是大于关系
对于带符号整数的比较 这个地方就是-7和6比,-3和5比,这个比较
就不能看CF了,而应该看OF跟SF 如果是OF等于SF的话表示大于关系
不等于表示小于关系,这个地方两个例子都是 不等于OF,不等于SF的,这个OF也是不等于SF的
所以呢我们可以看出这个-3和5相比 -3是小于5的,-7和6相比
-7是小于6的,所以是不相等应该是小于,而相等呢应该是大于
所以在指令当中这个条件转移指令j后面这些都是条件码
c就表示按进位来判断,nc
是进位标志这一位等于0,没有进位 那么进行转移。
这个e表示相等,equal就是相等 或者是零标志等于1,就是相等的时候如果做减法
减出来的结果是等于0,也就是ZF是等于1
表示相等,这时候转移,那么不相等的时候转移,那说明ZF等于0的时候转移
然后还有js、 jns、 jo和jno
那么也是表示SF这个标志如果是1转移 SF这个标志等于0,就是not
n表示非0,符号位是等于 0,这个OF等于1的时候转移,OF等于0的时候转移
那么这一类是根据单个的标志进行转移的 还有一类是按无符号数进行比较
按无符号数进行比较就是这边,我们刚才讲过无符号数是根据
CF是0还是1确定它的大小的,两者相减,减出来没有借位的时候
那说明是大于关系,减下来如果有借位表示是小于关系
如果还要考虑0的话,那么这个地方比如说这个是大于a,这个地方表示
above,就是大于的意思,e呢表示等于,这个就表示大于或者等于的时候转移
那么大于就是CF等于0,或者等于0,那么就是ZF等于1
ZF等于1表示结果为0,两者相等
CF等于0表示大于关系,这是大于或等于转移
前面的j表示jump的意思,这个b就是below,表示小于,无符号数的小于
当CF等于1,表示小于,并且呢不等于0,仅仅是
满足小于关系,这个呢是小于或者等于 小于或者等于的时候要转移
四种指令是无符号整数比较的指令 还有一类是带符号整数比较进行转移的指令
它也分四种,当SF等于OF的时候我们刚才讲过了是大于关系
那么这个表示大于的时候转移 大于也就是说这两个相等,并且呢ZF等于0,也就是
这个结果不等于0,两者不相等 那么仅仅是大于关系,这个是表示大于或者等于
的关系,这是小于并且不等,这边是少了一个0
这是小于并且结果不等于0,那么也就是仅仅是小于的
关系,这是小于或者等于,这边就是小于或者等于
所以对于带符号数的这个比较,大于是用g表示的
小于的话是用l来表示的,它跟无符号数不一样,无符号数是用a表示大于,above
b就表示below,是小于,看到这个助记符是a和b的时候,那说明是按无符号数
来比,就是按这个标志比,看到g和l的时候
说明是按带符号数来比,那么它比的这个标志就是这些信息 后面我们会举相应的例子来理解
这个例子实际上是一个求和,对a这个数组 进行求和的。
这个数组的元素个数呢是用len来表示的。
在这里面先对sum进行初始化, 然后呢对所有的数组元素依次相加,加到
结果当中去,最后呢返回。
所以这是一个求和的函数。
在这个函数当中,如果我们这边的参数len等于0的时候,
这边的这个返回值按道理应该是一个数组元素都不加, 那么这时候呢返回应该是0。
按道理len等于0的话, 这个for循环就是不执行,直接返回0。
但是我们在机器上执行的时候,却发生了存储器异常。
这种存储器异常到底是怎么发生的呢? 我们来看一下这个高级语言的源程序对应的指令 序列。
在这个指令序列当中,它对应的 len-1 这个运算用的是一个减法指令。
这个edx里面很显然是存放的len这个变量。
然后呢减1,减出来的结果再和这个 i
去比较,这个edx里面放的实际上是len-1。
eax里面显然放的是 i。
所以这边是用 i 和 len-1 去相减。
比较的时候我们前面讲过是通过 做减法来进行比较的。
i 减去len-1,
减出来的结果如果是小于或者等于的话,就继续进行 循环里面的数据。
jbe跟 .L3继续进行循环里面的数据的累加。
否则的话那就跳出来执行下面的这个语句。
所以这个地方我们可以看出,条件转移指令 用的是be这个条件,就是below or
equal, 小于或等于这个地方。
很显然这个是按无符号数来比的。
为什么按无符号数来比呢?因为在这个表达式当中,有一个变量len是无符号数, unsigned变量。
在C语言里面规定,在一个表达式当中 只要有一个变量是无符号数变量,unsigned的,
那么所有的运算都按无符号数进行运算。
所以这边比较的结果按无符号数来比。
按无符号数来比,为什么就会发生异常呢? 很显然我们刚才已经讲过了,
现在的这个eax里面放的是i,edx里面放的是 len,减去1以后还是放在edx里面。
所以第一次循环的时候,因为我们说len这个参数是0,
所以在edx里面,这个里面应该是放的是0。
然后呢第一次循环的时候,i 初始是等于0,因此eax里面也是0。
当这两个寄存器都是0的时候,我们看这三条指令的执行结果是什么样子的。
subl指令的执行结果,cmpl指令的执行结果,我们来看一看。
subl指令的执行实际上是在这样的一个,前面我们讲的整数加减运算器里面进行的。
这时候edx里面是全0,然后呢和1相减。
因此我们知道在这个里面,A这个输入端实际上送的是32个0。
B这个输入端送的是1,也就是0000 0001。
然后做的是减法,这条指令是减法指令。
所以 Sub是1。
Sub是1的时候就把这一路选过来, 这个B呢是等于000001,因此这个地方
个位取反以后就是111110。
那么这样的话在这个加法器里面做的加法就是 全0加上111110
和最后一个Sub等于1相加,那么很显然这个加出来的结果应该是全1。
也就是说这个地方是len是全0,这个地方呢是 减1
,个位取反以后就是 10,这是1,所以这两个数字相加
再加上末位的1,应该是全1,32个1。
32个1也就是8个F,因此这条指令执行结束
以后,edx减1以后还是送到edx,那么edx的内容应该是全1。
那么这两种形式实际上是完全等价的,我们可以最后用一个H 表示16进制,或者用0x表示16进制,完全一样的。
下面一条指令就是比较指令,cpml指令。
edx里面我们 刚才看到了它是全1了,用0减1以后得到全1。
eax里面是 i ,第一次循环的时候初值是等于0。
所以我们把 i 和 len-1 去进行比较。
比较的时候在这个里面是eax减去edx的内容,所以这个地方应该是 i 是全0。
这个地方呢应该是全1,因为这边是比较,比较的话
一定是做减法的,所以这个地方应该选出来的是各位取反的值。
所以应该是各位取反,全1各位取反以后就是全0。
这呢是等于1,因为是做减法。
所以最后的这个Sum应该是等于000001。
所以最后的这个结果应该是000001。
在这个电路当中得到这个标志,CF应该是等于1,
为什么呢?因为CF是等于Co,
很显然这个地方Co最高位应该是没有进位的,然后异或Sub,
Sub当然是等于1,所以它是等于1,CF是等于1。
ZF=0,因为结果是不等于0的,不等于0所以ZF等于0。
OF是等于0,因为这个地方相当于0和0相加,
也就是正数和正数相加,结果还是正数,所以 没有溢出,OF=0。
SF=0是因为这个地方的最高位 符号位是等于0,所以我们就得到1000四个标志。
下面一条指令,cmpl指令,比较指令后面就是jbe指令,刚才我们讲过了
是小于或者等于的时候就转到循环里面去继续执行。
我们来看看jbe指令, 它实际上是根据CF的值和ZF的值来判断的。
这是表示小于等于, 也就是说当CF=1,或者ZF=1的时候这个条件满足,满足的话就
转循环体里面执行。
那么我们来看看刚才jbe条件转移指令之前的一条指令的执行结果
是得到了一串这个标志信息,刚才我们讲的是1000。
我们可以看到是1000。
1000。
对照这个转移 条件的话,我们知道它是满足这个条件的,CF=1,
或者ZF=1,前面的这个已经满足了,执行这条
指令的时候,满足条件就意味着要转到L3 这个地方继续执行。
这个L3那个地方实际上就是循环体。
就是我们刚才看到的 继续转这个执行。
也就是说这个条件是满足的,继续执行。
实际上我们可以看到在这个循环里面累加, 下一次循环的时候
i 呢就等于1,然后再把1和这个
全1,1111比,因为我们刚才看到 len 0-1的结果是全1。
1和它比,很显然按无符号比的话还是比它小。
实际上这个i一直加1, i=2、 =3、
=4,每一个 i 都会比这个全1 要小或者小于等于。
因此这个循环是一个死循环。
就是这个循环会一直永远在做。
因此最终 i 很大的时候,
我们访问存储器的时候就会访问到一个很大的一个地址上面去,这个地址 很可能就是这个程序不能访问的地方。
因此出现了存储器访问异常, 这是我们刚才解释的,len-1得到的是32个1,
所以循环体呢就会不断地执行。
最后呢会导致数组访问越界而发生 存储器访问的异常。
如果我们把unsigned改成int型的,
我们在这里面进行比较的时候,它就没有问题了,就没有问题了。
如果是这个len改成int型的话,那么在这个里面比较的时候
它第一次就不满足条件,它就会直接执行这个return,return的话是能够按这个- 0正常返回。
其实这个里面就一个地方有差别,大家应该还记得那条指令叫
jbe,这个b就表示是按无符号比。
而这个地方len改成int型以后, 这个地方就是jle,l就是带符号数比较了。
因为在这个表达式当中,i 和len都是int型的,
所以它就解释成按int型的,也就是带符号整数来比较。
按带符号整数来比较的时候,就不是看CF了。
那么在这个程序当中,实际上前面的这条指令和这条指令, 执行的结果和前面unsigned的情况下应该是完全一样的。
也就是说 在cmpl执行完了以后得到的这个标志 也是应该等于1000,只不过在这条指令
解释的时候不像前面的那种用CF来解释了, 而是用其他的标志来解释。
我们来看一下,在jle的情况下, 它不是用jbe下面的CF来解释它大小,
比较的结果,而是用SF和OF的值来比较。
当这两个不相等或者ZF=1的时候,说明小于等于。
那么我们来看一下这个里面,OF是等于0的,SF呢是等于0的。
所以这个地方不满足这个条件,并且呢ZF也不等于1,
因为ZF等于0,这两个条件都不满足,也就是说这条指令的条件不满足。
不满足就说明不需要再去执行循环体里面的语句,就跳出循环了。
这时候呢不满足就跳出循环。
跳出循环就return这个sum应该是等于0。
就能够得到正确的结果。
[音乐] [音乐]