当我在谈汇编的时候我该谈些什么-流程控制

顺序执行

首先当然是最简单的顺序执行了,来一个简单的例子。

现在有不知道多少个计算题(小学生时候都做过吧。)

10+15=

79+13=

21+12=

56-8=

26-1=

34-3=

56-2=

=================

作为碳基生物哦,我们是可以直接一道题一道题做下来的。写完第一个写第二个对吧,但是计算机,cpu这种硅基生命(存疑),是没这种功能的,需要一个东西去专门记录做到了哪一道题,否则他就不知道接下来去做哪一个了。

这个时候就是需要一个神奇的寄存器了,专门用来存放“程序执行到哪里了”的这么一个寄存器,eip寄存器。这个寄存器有一个特殊之处是,无法通过mov指令进行修改。

eip本质上的作用是,当在执行一条指令的时候,eip此时存储的是下一条将要执行的指令的地址。

if else

我们在c程序中,写一条if else这种条件语句,其实是个很ez的事情。但是到了汇编就不一样了,其实也很ez,只不过不熟悉罢了。

在汇编中,就是通过jmp指令,(其实c中的goto关键字反汇编过来就是jmp指令)。

它的功能就是直接跳转到某个地方,你可以往前跳转也可以往后跳转,跳转的目标就是jmp后面的标签,这个标签在经过编译之后,会被处理成一个地址,实际上就是在往某个地址处跳转,而jmp在CPU内部发生的作用就是修改eip,让它突然变成另外一个值,然后CPU就乖乖地跳转过去执行别的地方的代码了。

而if是怎么实现的呢?

global main

main:
    mov eax, 66
    cmp eax, 10                         ; 对eax和10进行比较
    jle mdzz            ; 小于或等于的时候跳转
    sub eax, 10
mdzz:
    ret

状态寄存器

到这里,有一个问题出现了,在汇编语言里面实现“先比较,后跳转”的功能时,后面的跳转指令是怎么利用前面的比较结果的呢?

这就涉及到另一个寄存器了。在此之前,先想一下,如果自己在脑子里思考同样的逻辑,是怎么样的?

  • 先比较两个数
  • 记住比较结果
  • 根据比较结果作出决定

好了,这里又来了一个“记住”的动作了。CPU里面也有一个专用的寄存器,用来专门“记住”这个cmp指令的比较结果的,而且,不仅是cmp指令,它还会自动记住其它一些指令的结果。这个寄存器就是:

eflags

名为“标志寄存器”,它的作用就是记住一些特殊的CPU状态,比如前一次运算的结果是正还是负、计算过程有没有发生进位、计算结果是不是零等信息,而后续的跳转指令,就是根据eflags寄存器中的状态,来决定是否要进行跳转的。

cmp指令实际上是在对两个操作数进行减法,减法后的一些状态最终就会反映到eflags寄存器中。

循环结构

上回说到C语言中if这样的结构,在汇编里对应的是怎么回事,实质上,这就是分支结构的程序在汇编里的表现形式。

实际上,循环结构相比分支结构,本质上,没有多少变化,仅仅是比较合跳转指令的组合的方式与顺序有所不同,所以形成了循环。

当然,这个说法可能稍微拗口了一点。说得简单一点,循环的一个关键特点就是:

  • 程序在往回跳转

细细想,好像有道理哦,如果程序每到一个位置就往前跳转,那就是死循环,如果是在这个位置根据条件决定是否要向前跳转,那就是有条件的循环了。

口说无凭,还是先来分析一下一个C语言的while循环:

(Talk is cheap, show me the code!)

int sum = 0;
int i = 1;
while( i <= 10 ) {
    sum = sum + i;
    i = i + 1;
}

想必这段程序多数人都非常熟悉了,当年自己第一次学习循环的时候就碰到这个题目,脑子短路了,心里总想着这不就是一个等差数列公式么,题目却强行出现在循环一章的后面,最后结果让人大跌眼睛,这是要我老老实实像SHAB一样去加啊。

跑题了,先大致总结一下这个程序的关键部分到底在干什么:

  • \1. 比较i和10的大小
  • \2. 如果i <= 10则执行代码块,并回到(1)
  • \3. 如果不满足 i <= 10,则跳过代码块

好了,按照这个逻辑,在C语言中不使用循环怎么实现?其实也非常简单:

int sum = 10;
int i = 1;

_start:
if( i <= 10 ) {
    sum = sum + i;
    i = i + 1;
    goto _start;
}

这还不够,我们还得做一次变形,为什么呢?回想一下前面说的分之程序在汇编里的情况:

if ( a > 10 ) {
    // some code
}

上述C代码,暂且成为“正宗C代码”,等价的汇编大致结构如下:

cmp eax, 10
jle out_of_block

; some code

out_of_block:

再等价变换回C语言,这里把这种风格叫做“山寨C代码”,实际上就是这样的:

if( a <= 10 ) goto out_of_block;

// some code

out_of_block:

经过比较,我们可以发现“山寨C代码”和“正宗C代码”之间的一些区别:

  • 山寨版中,if块里只需要放一条跳转语句即可
  • 山寨版中,if里的条件是反过来的
  • 山寨版中,跳转语句的功能是跳过“正宗C代码”的if块

相当于是:不满足条件就跳过if中的语句块。

那循环呢?咱们把循环的C等价代码做一次变换,也就是把只含有goto和if的“正宗C代码”变换为“山寨C代码”的形式:

int sum = 10;
int i = 1;

_start:
if( i > 10 ) {
    goto _end_of_block;
}

sum = sum + i;
i = i + 1;
goto _start;

_end_of_block:

大致看一下流程,再对比源代码:

int sum = 0;
int i = 1;
while( i <= 10 ) {
    sum = sum + i;
    i = i + 1;
}

自己在脑子里面模拟一遍,是不是就能发现什么了?这俩货分明就是一个东西,执行的顺序和过程完全就是一样的。

到这里,我们的循环结构,全都被拆散成了最基本的结构,这种结构有一个关键的特点:

  • 所有if块中都仅有一条goto语句,别的啥都没了

到这里,本段就到位了。

用汇编写出循环

前面已经介绍了“如何把一个循环拆解成只有if和goto的结构”,有了这个结构之后,其实要写出汇编就非常容易了。

继续看山寨版的循环:

int sum = 10;
int i = 1;

_start:
if( i > 10 ) {
    goto _end_of_block;
}

sum = sum + i;
i = i + 1;
goto _start;

_end_of_block:

其实,稍微仔细一点就能发现,把这玩意儿写成汇编,就是逐行翻译就完事儿了。动手:

global main

main:
    mov eax, 0
    mov ebx, 1
_start:
    cmp ebx, 10
    jg _end_of_block
    
    add eax, ebx
    add ebx, 1
    jmp _start
    
_end_of_block:
    ret

这里面其实有一个套路:

  • 单条goto语句可以直接用jmp语句替代
  • if和goto组合的语句块可以用cmp和j*指令的组合替代

最后,其它语句该干啥干啥。

这?竟然?就?用汇编?写出?循环?来了?

嗯,是的。不需要任何一个新的指令,全都是前面提及过的基本指令,只是套路不一样了而已。

其实这就是一个套路,稍微总结一下就能发现,一个将while循环变换为汇编的过程如下:

  • 将while循环拆解成只有if和goto的形式
  • 将if形式的语句拆解成if块中仅有一行goto语句的形式
  • 从前往后逐行翻译成汇编语言

其它循环呢?

那while循环能够搞定了,其它类型的呢?do-while循环、for循环呢?

其实,在C语言中,这三种循环之间都是可以相互变换的,也就是说for循环可以变形成为while循环,while循环也可以变成for循环。举个例子:

int i = 1;
int sum = 0;
for(i = 0; i <= 10; i ++) {
    sum = sum + i;
}
int sum = 0;
int i = 1;
while( i <= 10 ) {
    sum = sum + i;
    i = i + 1;
}

上述两个片段的代码,其实就是等价的,仅仅是形式不同。只是有的循环思路用for循环写出来好看一些,有的思路用while循环写出来好看一些,别的没什么本质区别,经过编译器一倒腾之后,就更没有任何区别了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值