一、CPU 是如何執行指令的?
1、軟件程序員的理解
寫好的代碼變成了指令之后、是一條條順序執行的就可以了
2、CPU的邏輯組成

3、寄存器
N 個觸發器或者鎖存器,就可以組成一個 N 位(Bit)的寄存器,能夠保存 N 位的數據。比方說,我們用的 64 位 Intel 服務器,寄存器就是 64 位的。
4、特殊寄存器

5、CPU執行指令流程

1、CPU會根據PC寄存器里的地址,從內存里面把需要執行的指令讀取到指令寄存器里面直面執行
2、然后根據指令長度自增、開始順序讀取下一條指令。可以看到一個程序的一條條指令在內存里面是連續保存的。也會一條條順序加載
3、而有些特殊指令(J類跳轉指令)、會修改寄存器里面的地址
4、這樣下一條要執行的指令就不是從內存里面順序加載的
5、事實上、這些跳轉指令存在,也就是我們在寫程序的時候,使用了 if…else 條件語句和 while/for 循環語句的原因
二、從 if…else 來看程序的執行和跳轉
[root@luoahong c]# cat test.c
#include <time.h>
#include <stdlib.h>
int main()
{
srand(time(NULL));
int r = rand() % 2;
int a = 10;
if (r == 0)
{
a = 1;
} else {
a = 2;
}
我們用 rand 生成了一個隨機數 r,r 要么是 0,要么是 1。當 r 是 0 的時候,我們把之前定義的變量 a 設成 1,不然就設成 2。
[root@luoahong c]# gcc -g -c test.c
test.c: In function ‘main’:
test.c:15:3: error: expected declaration or statement at end of input
}
^
[root@luoahong c]# cat -n test.c
1 #include <time.h>
2 #include <stdlib.h>
3
4
5 int main()
6 {
7 srand(time(NULL));
8 int r = rand() % 2;
9 int a = 10;
10 if (r == 0)
11 {
12 a = 1;
13 } else {
14 a = 2;
15 }

執行報錯,是因為少了一個}
[root@luoahong c]# cat test.c
#include <time.h>
#include <stdlib.h>
int main()
{
srand(time(NULL));
int r = rand() % 2;
int a = 10;
if (r == 0)
{
a = 1;
} else {
a = 2;
}
}
[root@luoahong c]# gcc -g -c test.c
[root@luoahong c]# objdump -d -M intel -S test.o
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
#include <time.h>
#include <stdlib.h>
int main()
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 10 sub rsp,0x10
srand(time(NULL));
8: bf 00 00 00 00 mov edi,0x0
d: e8 00 00 00 00 call 12 <main+0x12>
12: 89 c7 mov edi,eax
14: e8 00 00 00 00 call 19 <main+0x19>
int r = rand() % 2;
19: e8 00 00 00 00 call 1e <main+0x1e>
1e: 99 cdq
1f: c1 ea 1f shr edx,0x1f
22: 01 d0 add eax,edx
24: 83 e0 01 and eax,0x1
27: 29 d0 sub eax,edx
29: 89 45 fc mov DWORD PTR [rbp-0x4],eax
int a = 10;
2c: c7 45 f8 0a 00 00 00 mov DWORD PTR [rbp-0x8],0xa
if (r == 0)
33: 83 7d fc 00 cmp DWORD PTR [rbp-0x4],0x0
37: 75 09 jne 42 <main+0x42>
{
a = 1;
39: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
40: eb 07 jmp 49 <main+0x49>
} else {
a = 2;
42: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
}
}
49: c9 leave
4a: c3 ret
可以看到,這里對於 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 Flah)
符號標志(SF,Sign Flag)以及溢出標志(OF,Overflow Flag),用在不同的判斷條件下。
cmp 指令執行完成之后,PC 寄存器會自動自增,開始執行下一條 jne 的指令。

三、如何通過 if…else 和 goto 來實現循環?
[root@luoahong c]# cat test.c
int main()
{
int a = 0;
for (int i = 0; i < 3; i++)
{
a += i;
}
}
[root@luoahong c]# gcc -g -c test.c
test.c: In function ‘main’:
test.c:4:5: error: ‘for’ loop initial declarations are only allowed in C99 mode
for (int i = 0; i < 3; i++)
^
test.c:4:5: note: use option -std=c99 or -std=gnu99 to compile your code
錯誤:使用gcc編譯代碼會報錯:

原因:這是因為gcc是基於c89標准,不能直接在for循環中初始化增量。而C99標准可以在for循環內定義變量。
解決方法:
我們再看一段簡單的利用 for 循環的程序。我們循環自增變量i 三次,三次之后,i>=3,就會跳出循環。整個程序,對應的 Intel 匯編代碼就是這樣的:
[root@luoahong c]# objdump -d -M intel -S test.o
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
int main()
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
int a = 0;
4: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0
int i;
for (i = 0; i < 3; i++)
b: c7 45 f8 00 00 00 00 mov DWORD PTR [rbp-0x8],0x0
12: eb 0a jmp 1e <main+0x1e>
{
a += i;
14: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
17: 01 45 fc add DWORD PTR [rbp-0x4],eax
for (i = 0; i < 3; i++)
1a: 83 45 f8 01 add DWORD PTR [rbp-0x8],0x1
1e: 83 7d f8 02 cmp DWORD PTR [rbp-0x8],0x2
22: 7e f0 jle 14 <main+0x14>
}
}
24: 5d pop rbp
25: c3 ret

可以看到,對應的循環也是用 1e 這個地址上的 cmp 比較指令,和緊接着的 jle 條件跳轉指令來、實現的。主要的差別在於,這里的 jle 跳轉的地址,在這條指令之前的地址 14,而非 if…else 編
譯出來的跳轉指令之后。往前跳轉使得條件滿足的時候,PC 寄存器會把指令地址設置到之前執行過的指令位置,重新執行之前執行過的指令,直到條件不滿足,順序往下執行 jle 之后的指令,整個循環才結束。
其實,你有沒有覺得,jle和jmp指令,有點像邏輯程序里面的goto命令,直接指定了一個特定條件下的跳轉位置
