一、循环控制语句的处理
上一篇博客说的都是顺序流程,现在分析一下循环流程的处理,for循环以及if条件等C语言程序的 流程控制 是如何实现的,我们还是以代码以及编译后的结果为例,看一下程序控制流程的处理过程:
// 定义MySub函数 void MySub(){ //不做任何处理 } // 定义MyFunc 函数 void MyFunc(){ int i; for(int i = 0;i < 10;i++){ //重复调用MySub十次 MySub(); } }
上述代码将局部变量 i 作为循环条件,循环调用十次 MySub 函数,下面式它主要的汇编代码:
xor ebx,ebx ; 将寄存器清0 @4 call _MySub ; 调用MySub函数 inc ebx ; ebx寄存器的值 + 1 cmp ebx,10 ; 将ebx寄存器的值和10进行比较 jl short @4 ; 如果小于10就跳到 @4
C 语言中的for 语句是通过在括号中指定循环计数器的初始值(i=0)、循环的继续条件(i<10)、循环计数器的更新(i++)这三种形式来进行循环处理的。与此相对的汇编代码就是通过比较指令(cmp)和跳转指令(jl)来实现的
下面我们来对上述代码进行说明
MyFunc 函数中用到的局部变量只有i,变量i申请分配了ebx寄存器的内存空间。for 语句括号中的i=0被转换为 xor ebx,ebx 这一处理,xor指令会对左起第一个操作数和右起第二个操作数进行XOR运算,然后把结果存储在第一个操作数中。由于这里把第一个操作数和第二个操作数都指定为了ebx,因此就变成了对相同数值的XOR运算。也就是说不管当前寄存器的值是什么,最终的结果都是0.类似的,我们使用 mov ebx,0也能得到相同的结果,但是xor指令的处理速度更快,而且编译器也会启动最优化功能
XOR指的就是异或操作,它的运算规则是如果a、b两个值不相同,则异或结果为1.如果a、b两个值相同,异或结果为0
相同数值进行XOR运算,运算结果为0.XOR的运算规则是,值不同时结果为1,值相同时结果为0.例如01010101和01010101 进行运算,就会分别对各个数字位进行XOR运算。因为每个数字位都相同,所以运算结果为0
ebx 寄存器的值初始化后,会通过call 指定调用_MySub函数,从_MySub函数返回后,会执行 inc ebx指令,对ebx的值进行+1操作,这个操作就相当于i++的意思,++表示的就是当前数值+1
这里需要知道i++ 和 ++i 的区别
i++ 是先赋值,赋值完成后再对 i 执行 +1操作
++i 是先进行 +1操作,完成后再进行赋值
inc 下一行的cmp是用来对第一个操作数和第二个操作数的数值进行比较的指令。cmp ebx,10就相当于C语言中的 i<10 这一处理,意思是把ebx寄存器的值与10进行比较。汇编语言中比较指令的结果,会存储在CPU的标志寄存器中。不过,标志寄存器的值,程序是无法直接参考的。那如何判断比较结果呢?
汇编语言中有多个 跳转指令,这些跳转指令会根据标志寄存器的值来判断是否进行跳转操作,例如最后一行的jl,它会根据cmp ebx,10指令所存储在标志寄存器中的值来判断是否跳转,jl 这条指令表示的就是 jump on less than(小于的话就跳转)。发现如果 i 比10小,就会跳转到@4所在的指令处继续执行
那么汇编代码的意思也可以用C语言来改写一下,加深理解
i ^= i; l4: MySub(); i++; if(i < 10) goto L4;
代码第一行 i^= i 指的是i和i进行异或运算,也就是XOR运算,MySub()函数用L4 标签来替代,然后进行 i 自增操作,如果 i的值小于10的话,就会一直循环 MySub()函数
二、条件分支的处理方法
条件分支的处理方式和循环的处理方式很相似,使用的也是cmp指令和跳转指令,下面用C语言编写的条件分支代码:
//定义MySub1 函数 void MySub1(){ //不作任何处理 } //定义MySub2 函数 void MySub2(){ //不作任何处理 } //定义MySub3 函数 void MySub3(){ //不作任何处理 } //定义MyFunc 函数 void MyFunc(){ int a = 123; //根据条件调用不同的函数 if(a > 100){ MySub1(); } else if(a < 50){ MySub2(); } else { MySub2(); } }
很简单的一个实现了条件判断的C语言代码,那Borland C++ 编译的结果如下:
_MyFunc proc near push ebp mov ebp,esp mov eax,123 ; 把123存入 eax 寄存器中 mov eax,100 ; 把eax寄存器的值同100进行比较 jle short @8 ; 比100小时,跳转到 @8标签 call _MySub1 ;调用MySub1函数 jmp short @11 ;跳转到@11标签 @8: cmp eax,50 ; 把 eax 寄存器的值同50进行比较 jge short @10 ; 比50大时,跳转到@10标签 call _MySub2 ;调用MySub2函数 jmp short @11 ;跳转到@11标签 @10: call _MySub3 ;调用MySub3函数 @11: pop ebp ret _MyFunc endp
上面代码用到了三种跳转指令,分别是 jle(jump on less or equal)比较结果小时跳转,jge(jump on greater or equal)比较结果大时跳转,还有不管怎样都会进行跳转的jmp,在这些跳转指令之前还有用来比较的指令 cmp,构成了上述汇编代码的主要逻辑形式