天天看點

重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

2 從if/else看程式的執行和跳轉

我們現在就來看一個包含if…else的簡單程式。

  • test.c
重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

用rand生成了一個随機數r(0/1)

  • 當r是0,我們把之前定義的變量a設成1
  • 不然就設成2

我們把這個程式編譯成彙編代碼。你可以忽略前後無關的代碼,隻關注于這裡的if…else條件判斷語句

對應的彙編代碼是這樣的

重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

對于r == 0的條件判斷,被編譯成了cmp和jne兩條指令。

  • cmp指令比較了前後兩個操作數的值

    DWORD PTR 代表操作的資料類型是32位的整數

    [rbp-0x4]則是一個寄存器的位址

    第一個操作數就是從寄存器裡拿到的變量r的值

    第二個操作數0x0就是我們設定的常量0的16進制表示

cmp指令的比較結果,會存入到條件碼寄存器

狀态寄存器又名條件碼寄存器,它是計算機系統的核心部件——運算器的一部分

狀态寄存器用來存放兩類資訊:

一類是展現目前指令執行結果的各種狀态資訊(條件碼),如有無進位(CF位)、有無溢出(OF位)、結果正負(SF位)、結果是否為零(ZF位)、奇偶标志位(P位)等

另一類是存放控制資訊(PSW:程式狀态字寄存器),如允許中斷(IF位)、跟蹤标志(TF位)等

有些機器中将PSW稱為标志寄存器FR(Flag Register)。

如果比較結果 True,即 r == 0,就把零标志條件碼(對應的條件碼是ZF,Zero Flag)設定為1

條件碼是CPU根據運算結果由硬體設定的位,展現目前指令執行結果的各種狀态資訊

例如:算術運算産生的正、負、零或溢出等的結果。條件碼可被測試,作為分支運算的依據,此外,有些條件碼可被設定,例如對于最高位進位标志C,可用指令對它置位和複位。

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,代表累加寄存器

在中央處理器中,累加器 (accumulator) 是一種寄存器,用來儲存計算産生的中間結果。如果沒有像累加器這樣的寄存器,那麼在每次計算 (加法,乘法,移位等等) 後就必須要把結果寫回到 記憶體,也許馬上就得讀回來。然而存取主存的速度是比從算術邏輯單元到有直接路徑的累加器存取更慢。

第二個操作數0x0則是16進制的0的表示。這條指令其實沒有實際的作用,它的作用是一個占位符

if條件如果滿足,在指派的mov指令執行完成之後,有一個jmp的無條件跳轉指令

跳轉的位址就是這一行的位址51

我們的main函數沒有設定傳回值,而mov eax, 0x0 其實就是給main函數生成了一個預設的為0的傳回值到累加器裡面

if條件裡面的内容執行完成之後也會跳轉到這裡,和else裡的内容結束之後的位置是一樣的。

重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

上一講我們講打孔卡的時候說到,讀取打孔卡的機器會順序地一段一段地讀取指令,然後執行。

執行完一條指令,它會自動地順序讀取下一條指令

如果執行的目前指令帶有跳轉的位址,比如往後跳10個指令,那麼機器會自動将卡片帶往後移動10個指令的位置,再來執行指令

同樣的,機器也能向前移動,去讀取之前已經執行過的指令

這也就是我們的while/for循環實作的原理。

如何通過if…else和goto來實作循環?

重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

我們再看一段簡單的利用for循環的程式。我們循環自增變量i三次,三次之後,i>=3,就會跳出循環。整個程式,對應的Intel彙編代碼就是這樣的:

重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

可以看到,對應的循環也是用1e這個位址上的cmp比較指令

和緊接着的jle條件跳轉指令來實作的

主要的差别在于,這裡的jle跳轉的位址,在這條指令之前的位址14,而非if…else編譯出來的跳轉指令之後

往前跳轉使得條件滿足的時候,PC寄存器會把指令位址設定到之前執行過的指令位置,重新執行之前執行過的指令,直到條件不滿足,順序往下執行jle之後的指令,整個循環才結束。

重學計算機組成原理(五)- "旋轉跳躍"的指令實作(下)2 從if/else看程式的執行和跳轉3 總結4 推薦閱讀參考

如果你看一長條打孔卡的話,就會看到卡片往後移動一段,執行了之後,又反向移動,去重新執行前面的指令。

jle和jmp指令,有點像程式語言裡面的goto指令,直接指定了一個特定條件下的跳轉位置

雖然我們在用進階語言開發程式的時候反對使用goto,但是實際在機器指令層面,無論是if…else…也好,還是for/while也好,都是用和goto相同的跳轉到特定指令位置的方式來實作的。

3 總結

學習了程式裡的多條指令,究竟是怎麼樣一條一條被執行的

除了簡單地通過PC寄存器自增的方式順序執行外

條件碼寄存器會記錄下目前執行指令的條件判斷狀态

然後通過跳轉指令讀取對應的條件碼

修改PC寄存器内的下一條指令的位址

最終實作if…else以及for/while這樣的程式控制流程。

雖然我們可以用進階語言,可以用不同的文法,比如 if…else 這樣的條件分支,或者 while/for 這樣的循環方式,來實作不用的程式運作流程

但是回歸到計算機可以識别的機器指令級别,其實都隻是一個簡單的位址跳轉而已,也就是一個類似于goto的語句。

想要在硬體層面實作這個goto語句,除了本身需要用來儲存下一條指令位址,以及目前正要執行指令的PC寄存器、指令寄存器外

我們隻需要再增加一個條件碼寄存器,來保留條件判斷的狀态。這樣簡簡單單的三個寄存器,就可以實作條件判斷和循環重複執行代碼的功能。

4 推薦閱讀

《深入了解計算機系統》的第3章

詳細講解了C語言和Intel CPU的彙編語言以及指令的對應關系,以及Intel CPU的各種寄存器和指令集。

Intel指令集相對于之前的MIPS指令集要複雜一些

所有的指令是變長的

從1個位元組到15個位元組不等

即使是彙編代碼,還有很多針對操作資料的長度不同有不同的字尾

參考

繼續閱讀