06 、指令跳转:原来if...else就是goto
写好的代码编译成指令之后,一般正常流程是一条一条的顺序执行的。但是在程序中总会用到if...else这样的条件判断语句、while和for循环语句,还有函数或者过程调用,所以遇到这些程序编译的指令时是不会平铺下去的。这是就是跳转执行,这就是指令跳转的功能。
下面看一下CPU的构成以及这些组成部分在程序执行过程中所起的作用,结构如图
一个CPU里面有很多种不同功能的寄存器,这里介绍三种比较特殊的。
第一个是PC寄存器(Program Counter Register),也叫指令地址寄存器(Instruction Address Register),就是用来存放下一条需要执行的计算机指令的内存地址。
第二个是指令寄存器(Instruction Register),用来存放当前正在执行的指令。
第三个是条件码寄存器(Status Register),用里面的一个一个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。
除了这些特殊的寄存器,CPU 里面还有更多用来存储数据和内存地址的寄存器。这样的寄存器通常一类里面不止一个。我们通常根据存放的数据内容来给它们取名字,比如整数寄存器、浮点数寄存器、向量寄存器和地址寄存器等等。有些寄存器既可以存放数据,又能存放地址,我们就叫它通用寄存器。
实际上,一个程序执行的时候,CPU 会根据 PC 寄存器里的地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。可以看到,一个程序的一条条指令,在内存里面是连续保存的,也会一条条顺序加载。
而有些特殊指令,比如上一讲我们讲到 J 类指令,也就是跳转指令,会修改 PC 寄存器里面的地址值。这样,下一条要执行的指令就不是从内存里面顺序加载的了。事实上,这些跳转指令的存在,也是我们可以在写程序的时候,使用 if…else 条件语句和 while/for 循环语句的根本原因。
从 if…else 来看程序的执行和跳转
if...else的简单程序
// test.c #include <time.h>
#include <stdlib.h> int main()
{
srand(time(NULL));
int r = rand() % ;
int a = ;
if (r == )
{
a = ;
} else {
a = ;
}
将其编译成汇编代码,只看if...else部分
if (r == )
3b: 7d fc cmp DWORD PTR [rbp-0x4],0x0
3f: jne 4a <main+0x4a>
{
a = ;
: c7 f8 mov DWORD PTR [rbp-0x8],0x1
: eb jmp <main+0x51>
}
else
{
a = ;
4a: c7 f8 mov DWORD PTR [rbp-0x8],0x2
: b8 mov eax,0x0
}
可以看到,这里对于 r == 0 的条件判断,被编译成了 cmp 和 jne 这两条指令。cmp 指令比较了前后两个操作数的值,这里的 DWORD PTR 代表操作的数据类型是 32 位的整数,而 [rbp-0x4] 则是一个寄存器的地址。所以,第一个操作数就是从寄存器里拿到的变量 r 的值。第二个操作数 0x0 就是我们设定的常量 0 的 16 进制表示。cmp 指令的比较结果,会存入到条件码寄存器当中去。
在这里,如果比较的结果是 True,也就是 r == 0,就把零标志条件码(对应的条件码是 ZF,Zero Flag)设置为 1。除了零标志之外,Intel 的 CPU 下还有进位标志(CF,Carry Flag)、符号标志(SF,Sign Flag)以及溢出标志(OF,Overflow Flag),用在不同的判断条件下。
cmp 指令执行完成之后,PC 寄存器会自动自增,开始执行下一条 jne 的指令。(待指令执行完后会自动增加到下一条指令的内存地址值)
跟着的 jne 指令,是 jump if not equal 的意思,它会查看对应的零标志位。如果为 0,会跳转到后面跟着的操作数 4a 的位置。这个 4a,对应这里汇编代码的行号,也就是上面设置的 else 条件里的第一条指令。当跳转发生的时候,PC 寄存器就不再是自增变成下一条指令的地址,而是被直接设置成这里的 4a 这个地址。这个时候,CPU 再把 4a 地址里的指令加载到指令寄存器中来执行。
跳转到执行地址为 4a 的指令,实际是一条 mov 指令,第一个操作数和前面的 cmp 指令一样,是另一个 32 位整型的寄存器地址,以及对应的 2 的 16 进制值 0x2。mov 指令把 2 设置到对应的寄存器里去,相当于一个赋值操作。然后,PC 寄存器里的值继续自增,执行下一条 mov 指令。
这条 mov 指令的第一个操作数 eax,代表累加寄存器,第二个操作数 0x0 则是 16 进制的 0 的表示。这条指令其实没有实际的作用,它的作用是一个占位符。我们回过头去看前面的 if 条件,如果满足的话,在赋值的 mov 指令执行完成之后,有一个 jmp 的无条件跳转指令。跳转的地址就是这一行的地址 51。我们的 main 函数没有设定返回值,而 mov eax, 0x0 其实就是给 main 函数生成了一个默认的为 0 的返回值到累加器里面。if 条件里面的内容执行完成之后也会跳转到这里,和 else 里的内容结束之后的位置是一样的。如果没有提供返回值,很多版本的编译器会隐式地生成一个return 0;的返回值,就会生成 mov eax, 0x0 的多出来的指令
上一讲我们讲打孔卡的时候说到,读取打孔卡的机器会顺序地一段一段地读取指令,然后执行。执行完一条指令,它会自动地顺序读取下一条指令。如果执行的当前指令带有跳转的地址,比如往后跳 10 个指令,那么机器会自动将卡片带往后移动 10 个指令的位置,再来执行指令。同样的,机器也能向前移动,去读取之前已经执行过的指令。这也就是我们的 while/for 循环实现的原理。
如何通过 if…else 和 goto 来实现循环?
看一点for循环的c程序代码
int main()
{
int a = ;
for (int i = ; i < ; i++)
{
a += i;
}
}
对应的Intel汇编
for (int i = ; i < ; i++)
b: c7 f8 mov DWORD PTR [rbp-0x8],0x0
: eb 0a jmp 1e <main+0x1e>
{
a += i;
14: 8b f8 mov eax,DWORD PTR [rbp-0x8]
: fc add DWORD PTR [rbp-0x4],eax
for (int i = ; i < ; i++)
1a: f8 add DWORD PTR [rbp-0x8],0x1
1e: 7d f8 cmp DWORD PTR [rbp-0x8],0x2
: 7e f0 jle 14 <main+0x14>
: b8 mov eax,0x0
}
可以看到,对应的循环也是用 1e 这个地址上的 cmp 比较指令,和紧接着的 jle 条件跳转指令来实现的。主要的差别在于,这里的 jle 跳转的地址,在这条指令之前的地址 14,而非 if…else 编译出来的跳转指令之后。往前跳转使得条件满足的时候,PC 寄存器会把指令地址设置到之前执行过的指令位置,重新执行之前执行过的指令,直到条件不满足,顺序往下执行 jle 之后的指令,整个循环才结束。
其实,你有没有觉得,jle 和 jmp 指令,有点像程序语言里面的 goto 命令,直接指定了一个特定条件下的跳转位置。虽然我们在用高级语言开发程序的时候反对使用 goto,但是实际在机器指令层面,无论是 if…else…也好,还是 for/while 也好,都是用和 goto 相同的跳转到特定指令位置的方式来实现的。
总结
这一节,我们在单条指令的基础上,学习了程序里的多条指令,究竟是怎么样一条一条被执行的。除了简单地通过 PC 寄存器自增的方式顺序执行外,条件码寄存器会记录下当前执行指令的条件判断状态,然后通过跳转指令读取对应的条件码,修改 PC 寄存器内的下一条指令的地址,最终实现 if…else 以及 for/while 这样的程序控制流程。
你会发现,虽然我们可以用高级语言,可以用不同的语法,比如 if…else 这样的条件分支,或者 while/for 这样的循环方式,来实现不用的程序运行流程,但是回归到计算机可以识别的机器指令级别,其实都只是一个简单的地址跳转而已,也就是一个类似于 goto 的语句。
想要在硬件层面实现这个 goto 语句,除了本身需要用来保存下一条指令地址,以及当前正要执行指令的 PC 寄存器、指令寄存器外,我们只需要再增加一个条件码寄存器,来保留条件判断的状态。这样简简单单的三个寄存器,就可以实现条件判断和循环重复执行代码的功能。
推荐资料
《深入理解计算机系统》的第 3 章,详细讲解了 C 语言和 Intel CPU 的汇编语言以及指令的对应关系,以及 Intel CPU 的各种寄存器和指令集。
06 、指令跳转:原来if...else就是goto的更多相关文章
- ARM学习笔记2——分支跳转指令
一.Arm指令条件码和条件助记符 二.跳转指令B 1.作用 跳转指令B使程序跳转到指定的地址执行程序(跳转范围是PC-32MB到PC+32MB) 2.指令格式(注:B后面如果有条件,条件就是紧跟在B后 ...
- call 和 ret 指令
body, table{font-family: 微软雅黑; font-size: 13.5pt} table{border-collapse: collapse; border: solid gra ...
- arm汇编指令
ARM处理器的指令集可以分为跳转指令.数据处理指令.程序状态寄存器(PSR)处理指令.加载/存储指令.协处理器指令和异常产生指令6大指令 一.跳转指令 跳转指令用于实现程序流程的跳转 跳转指令分类 Ⅰ ...
- ARM指令教程
ARM指令教程 ARM汇编程序特点: l 所有运算处理都是发生通用寄存器(一般是R0~R14)的之中.所有存储器空间(如C语言变量的本质就是一个存储器空间上的几个BYTE).的值的处理 ...
- ARM指令
语法格式 <opcode>{<cond>}{S} <Rd>, <Rn>,<shifter_operand> {}表示是可选的部分,<& ...
- ARM指令学习,王明学learn
ARM指令学习 一.算数和逻辑指令 1— MOV 数据传送指令 2.— MVN 数据取反传送指令 3.— CMP 比较指令 4.— CMN 反值比较指令 5.— TST 位测试 ...
- Java系列:JVM指令详解(下)(zz)
九.自增减指令 20:iconst_1 21:istore_1 22:return 指令码 助记符 ...
- ARM 汇编指令
ARM汇编程序特点: l 所有运算处理都是发生通用寄存器(一般是R0~R14)的之中.所有存储器空间(如C语言变量的本质就是一个存储器空间上的几个BYTE).的值的处理,都是要传送到通 ...
- arm汇编指令总结(不断更新)
/** ****************************************************************************** * @author Maox ...
随机推荐
- Angular应用架构设计-3:Ngrx Store
这是有关Angular应用架构设计系列文章中的一篇,在这个系列当中,我会结合这近两年中对Angular.Ionic.甚至Vuejs等框架的使用经验,总结在应用设计和开发过程中遇到的问题.和总结的经验, ...
- session 在PC端正常设置读取,在移动端无法正常读取
一.背景 最近在做一个面向三端[H5.IOS.安卓]的短信验证码登录接口.发送短信验证码时,服务端通过 session 保存验证码的值.登录时,从 session 获取验证码和用户输入的验证码 相比较 ...
- JAVA基础之Servlet
个人理解: servlet是用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容.需要注意的在创建的服务器端的目录和实际上存在差别的,并且访 ...
- viewer与 iview Carousel(走马灯) 结合使用,图片无法显示
问题原因:iview Carousel (走马灯)在加载是如果没有图片 它没有高度.之后给src赋值,图片无法显示,拖动一下浏览器宽或搞它就会显示. 走马灯解决这个问题方法:1在Carousel或其父 ...
- SQL SERVER-Extendevent检测TempDB增长
--创建Session收集数tempDB增长数据 CREATE EVENT SESSION [Database_Growth_Watchdog] ON SERVER ADD EVENT sqlserv ...
- 虚拟机搭建IKUAI软路由
1.登录爱快软路由的官网下载镜像(支持ISO ,GHO),这里采用iso安装 2.选择好后开机(选择数字编号1,回车) 3.输入“y”回车,程序自动安装 4.安装成功后如图 5.设置IP 6.绑定网卡 ...
- k8s 如何支持私有镜像
k8s如何支持私有镜像 实现无密钥编排 kubectl create secret docker-registry regsecret --docker-server=registry.cn-shen ...
- windows系统开机执行文件
以下都是在windows系统下执行的 开机自启程序 新建xxx.bat的文件 编辑自己的bat文件,将所要加载的项目引导进来 注意: 如果有相对路径的话,就需要先加载项目,在用python 执行脚本程 ...
- SoC的设计变的如此复杂和高成本
当一些硬件IP变成了标准的螺丝钉和螺母的时候,硬件设计的未来就没有了吗? 由于太过于复杂,而整体的毛利率又不高,无法迅速迭代,产生边际效应,也无法迅速扩张. 就成了一个传统行业,从业者也逐渐被时代遗忘 ...
- P1341 无序字母对[欧拉路]
题目描述 给定n个各不相同的无序字母对(区分大小写,无序即字母对中的两个字母可以位置颠倒).请构造一个有n+1个字母的字符串使得每个字母对都在这个字符串中出现. 解析 毒瘤字符串读入 我就是不喜欢邻接 ...