Skip to main content

06 指令跳转

CPU 如何执行指令

写好的代码变成了指令之后,是一条一条顺序执行的 。

CPU 是由一堆寄存器组成的。寄存器是 CPU 内部,由多个触发器(Flip-Flop)或者锁存器(Latches)组成的简单电路。

触发器和锁存器,是两种不同原理的数字电路组成的逻辑门。

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

PC 寄存器(Program Counter Register)

也叫指令地址寄存器,用来存放下一条需要执行的计算机指令的内存地址。

指令寄存器(Instruction Register)

用来存放当前正在执行的指令。

条件码寄存器(Status Register)

用里面的一个一个标记位(Flag),存放 CPU 进行算术或者逻辑计算的结果。

存储数据和内存地址寄存器

存储数据和内存地址

根据存放的数据内容:整数寄存器、浮点数寄存器、向量寄存器和地址寄存器等等

既可以存放数据,又能存放地址:通用寄存器

一个程序执行的时候,CPU 会根据 PC 寄存器里的地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。一个程序的一条条指令,在内存里面是连续保存的,也会一条条顺序加载。

有些特殊指令 (J 类指令),也就是跳转指令,会修改 PC 寄存器里面的地址值。下一条要执行的指令就不从内存里面顺序加载。这些跳转指令的存在,也是我们可以在写程序的时候,使用 if…else 条件语句和 while/for 循环语句的原因。

程序的执行和跳转

int r = rand() % 2;
int a = 10;
if (r == 0)
{
a = 1;
} else {
a = 2;
}

$ gcc -g -c test.c

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


if (r == 0)
3b: 83 7d fc 00 cmp DWORD PTR [rbp-0x4],0x0
3f: 75 09 jne 4a <main+0x4a>
{
a = 1;
41: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
48: eb 07 jmp 51 <main+0x51>
}
else
{
a = 2;
4a: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
51: b8 00 00 00 00 mov eax,0x0
}

cmp:指令比较前后两个操作数的值

DWORD PTR:操作的数据类型是 32 位的整数

[rbp-0x4]:变量 r 的内存地址

DWORD PTR [rbp-0x4]:从内存里拿到的变量 r 的值

0x0:常量 0 的 16 进制

条件码寄存器:存放 cmp 指令的比较结果

jne: jump if not equal

eax:累加寄存器

Intel CPU 的条件码 :零标志(ZF,Zero Flag),进位标志(CF,Carry Flag)、符号标志(SF,Sign Flag),溢出标志(OF,Overflow Flag),用在对应的判断条件下。

cmp 指令执行完成之后,PC 寄存器会自动自增,开始执行下一条 jne 的指令。

实现循环

int main()
{
int a = 0;
for (int i = 0; i < 3; i++)
{
a += i;
}
}
    for (int i = 0; i <= 2; i++)
b: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
12: eb 0a jmp 1e
{
a += i;
14: 8b 45 f8 mov eax,DWORD PTR [rbp-0x4]
17: 01 45 fc add DWORD PTR [rbp-0x8],eax

1a: 83 45 f8 01 add DWORD PTR [rbp-0x4],0x1
1e: 83 7d f8 02 cmp DWORD PTR [rbp-0x4],0x2
22: 7e f0 jle 14
24: b8 00 00 00 00 mov eax,0x0
}