06 | 指令跳转:原来if...else就是goto

不只有 int a = 1 总是要用到 if…else 、while 和 for 还有函数或者过程调用。

CPU 执行的也不只是一条指令,因为有 if…else、for 这样的条件和循环存在,指令不会一路平铺直叙地执行下去。程序是怎么被分解成一条条指令来执行的?

CPU是如何执行指令的?

程序员只要知道,代码变指令后,一条一条顺序执行就可以。

CPU 是一堆寄存器组成,寄存器由多个触发器(Flip-Flop)或者锁存器(Latches)组成简单电路,两种不同原理的数字电路组成的逻辑门。

N 个触发器或者锁存器,就可组成一个 N 位(Bit)的寄存器,能保存 N 位数据。 64 位 Intel 服务器,寄存器就是 64 位的。

一个 CPU 会有很多种不同功能寄存器。三种特殊的:

(1)PC 寄存器(Program Counter Register),也叫指令地址寄存器(Instruction Address Register)。存放下一条要执行的计算机令的内存地址

(2)指令寄存器(Instruction Register),存放当前正在执行指令。

(3)条件码寄存器(Status Register),一个个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。

(4)其他:整数浮点数向量、地址寄存器等等。有些可以存放数据,又能存放地址,就叫通用寄存器。

程序执行时,CPU 根据 PC 寄存器里的地址,从内存里面把需要执行的指令(连续保存)读取到指令寄存器里面执行,根据指令长度自增顺序读下一条

特殊指令,上一讲 J 类指令(跳转指令),会修改 PC 寄存器里面的地址值下一条要执行的指令就不是从内存里面顺序加载的了。使用 if…else 条件语句和 while/for 循环语句的原因。

二、从 if…else 来看程序的执行和跳转

包含 if…else 的简单程序。

rand 生成一个随机数 r,r 是 0/ 1。 r 是 0时,把变量 a 设成 1,不然就设成 2。

$ gcc -g -c  test.c

$ objdump -d -M  intel -S test.o

编译成汇编代码。忽略前后无关的代码,关注于这里的 if…else 条件判断语句:

r == 0 的条件判断,被编译成了 cmp 和 jne 两条指令

(一)cmp 指令:比较两个操作数值,比较结果,存入条件码寄存器

    (1)DWORD PTR :操作数据类型是 32 位整数

    (2)[rbp-0x4] :寄存器的地址。

    (3)第一个操作数:寄存器里拿到的变量 r 的值第二个操作数 0x0 就是我们设定的常量 0 16 进制表示

    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 (对应汇编代码行号, else 条件里的第一条指令)。跳转时(mov 指令),PC 寄存器:不再是自增变成下一条指令的地址,而是被直接设置成 4a 地址,指令寄存器执行4a 地址。

mov 指令:(1)和 cmp 一样, 32 位整型寄存器地址,对应的 2 的 16 进制值 0x2。

(2)4a这条指令:把 2 设置到对应的寄存器里(赋值),PC 寄存器里的值自增,执行下一条 mov 指令。

(3)51这条指令:占位符,没实际作用,第一个操作数 eax:寄存器, 0x0:16 进制的 0 。

 if 条件,如满足,

赋值的 mov 指令(41)执行完, jmp无条件跳转 51。 main 函数没有设定返回值,而 mov eax, 0x0其实就是给 main 函数生成了一个默认的为 0 的返回值到累加器里面。if 条件里面的内容执行完成之后(和 else 一样)跳转到这里,。

读取打孔卡机器顺序一段一段读取指令,执行。有跳转地址,比如往后跳 10 个指令,机器自动将卡片带往后移动 10 个指令的位置再执行指令。也能向前移动, while/for 循环实现原理。

如何通过 if…else goto 来实现循环?

 for 循环的程序。三次之后,i>=3,就会跳出循环。

循环用 1e 地址上 cmp 比较指令,和紧接着的 jle 条件跳转指令来实现的。差别: jle 跳转的地址,在这条指令之前的地址 14,而非 if…else 编译出来的跳转指令之后。条件满足,PC 寄存器会把指令地址设置到之前执行过的指令位置,重新执行,直到条件不满足,执行 jle 之后的指令,循环才结束。

如果你看一长条打孔卡的话,就会看到卡片往后移动一段,执行了之后,又反向移动,去重新执行前面的指令。

jle 和 jmp 指令,有点像 goto 命令,指定跳转位置。虽反对使用 goto,但实际机器指令层面 if…else, for/while ,都是用和 goto 相同方式实现。

总结延伸

这一节,在单条指令的基础上,学习了多条指令,怎么样一条条被执行。 PC 寄存器自增的方式顺序执行条件码寄存器记录下当前执行指令的条件判断状态,然后通过跳转指令读取对应的条件码修改 PC 寄存器内的下一条指令的地址,最终实现 if…else 以及 for/while 这样的程序控制流程。类似 goto 语句。

硬件层面实现goto 语句,要保存下一条指令地址,以及当前正要执行指令的 PC 寄存器、指令寄存器,条件码寄存器,保留条件判断的状态。三个寄存器就可以实现。

你可能感兴趣的:(06 | 指令跳转:原来if...else就是goto)