順序結構
資料傳送指令
我們都清楚,絕大多數編譯器都把組合語言作為中間語言,把組合語言程式變成可執行的二進位制檔案早就解決了,所以現在的高階語言基本上只需要把自己翻譯成組合語言就可以了。
彙編指令總共只有那麼多,大多數指令都是對資料進行操作,比如常見的資料傳送指令mov
。不難理解,被運算元據無非有三種形式,立即數,即用來表示常數值;暫存器,此時的資料即存放在指定暫存器中的內容;記憶體引用,它會根據計算出來的位址訪問某個記憶體位置。
需要注意的是,到了彙編層級,就不像高階語言那樣隨隨便便int
就能和long
型別的資料相加減,他們在底層所占有的位元組是不一樣的,彙編指令是區分運算元據大小的,比如資料傳送指令,就有下面這些品種(x86-64 對資料傳送指令加了一條限制:兩個運算元不能都指向記憶體位置)。
壓棧與彈棧
對於棧,我想不必多講,it 行業的同學都清楚,它是一種線性資料結構,其中的資料遵循「先進後出」原則,暫存器%rsp
儲存著棧頂元素的位址,即棧頂指標。乙個程式要執行起來,離不開棧這種資料結構。
棧使用最多的就是彈棧popq
和壓棧pushq
操作。比如將乙個四字值壓入棧中,棧頂指標首先要減 8(棧向下增長),然後將值寫到新的棧頂位址;而彈棧則需要先將棧頂資料讀出,然後再將棧指標加 8。所以pushq
和popq
指令就可以表示為下面的形式。
// 壓棧
subq $8, %rsp
movq %rbp, (%rsp)
// 彈棧
movq (%rsp), %rax
addq $8, %rsp
複製**
條件結構
前面講的都是順序結構,我們的程式中不可能只有順序結構,條件結構是必不可缺的元素,那麼彙編又是如何實現條件結構的呢?
首先你需要知道,除了整數暫存器,cpu 還維護著一組條件碼暫存器,我們主要是了解如何把高階語言的條件結構轉換為組合語言,不去關注這些條件碼暫存器,只需要知道彙編可以通過檢測這些暫存器來執行條件分支指令。
if-else 語句
下面是 c 語言中的if-else
語句的通用形式。
if(test-expr)else
複製**
組合語言通常會將上面的 c 語言模板轉換為下面的控制流形式,只要使用條件跳轉和無條件跳轉,這種形式的控制流就可以和彙編**一一對應,我們以 c 語言形式給出。
t = test-expr;
if(!t)
then-statement;
goto done;
false:
else-statement;
done:
複製**
但是這種條件控制轉移形式的**在現代處理器上可能會很低效。原因是它無法事先確定要跳轉到哪個分支,我們的處理器通過流水線來獲得高效能,流水線的要求就是事先明確要執行的指令順序,而這種形式的**只有當條件分支求值完成後,才能決定走哪乙個分支。即使處理器採用了非常精密的分支**邏輯,但是還是有錯誤**的情況,一旦**錯誤,那將會浪費 15 ~ 30 個時鐘週期,導致效能下降。
在流水線中,把一條指令分為多個階段,每個階段只執行所需操作的一小部分,比如取指令、確定指令型別、讀資料、運算、寫資料以及更新程式計數器。流水線通過重疊連續指令的步驟來獲得高效能,比如在取一條指令的同時,執行它前面指令的算術運算。所以如果事先不知道指令執行順序,那麼事先所做的預備工作就白幹了。為了提高效能,可以改寫成使用條件資料傳送的**,比如下面的例子。
v = test-expr ? then-expr : else-expr;
// 使用條件資料傳送方法
v = then-expr;
ve = else-expr;
t = test-expr;
if(!t)
複製**
這樣改寫,就能提高程式的效能了,但是並不是所有的條件表示式都可以使用條件傳送來編譯,一般只有當兩個表示式都很容易計算時,編譯器才會採用條件資料傳送的方式,大部分都還是使用條件控制轉移方式編譯。
switch 語句
switch
語句可以根據乙個整數索引值進行多重分支,在處理具有多種可能結果的測試時,這種語句特別有用。為了讓switch
的實現更加高效,使用了一種叫做跳轉表的資料結構(radis 也是用的跳表)。跳轉表是乙個陣列,表項 i 是乙個**段的位址,當開關情況數量比較多的時候,就會使用跳轉表。
我們舉個例子,還是採用 c 語言的形式表是控制流,要理解的是執行switch
語句的關鍵步驟就是通過跳轉表來訪問**的位置。
void
switch_eg
(long x, long n, long *dest)
*dest = val;
}複製**
要注意的是,上面的**中有的分支沒有break
,這種問題在筆試中會經常遇到,沒有break
會繼續執行下面的語句,即變成了順序執行。上面的**會被翻譯為下面這種控制流。
void
switch_eg
(long x, long n, long *dest)
; unsigned
long index = n - 100;
long val;
if(index > 6)
goto *jt[index];
loc_a:
val = x * 13;
goto done;
loc_b:
x = x + 10;
loc_c:
val = x + 11;
goto done;
loc_d:
val = x * x;
goto done;
loc_def:
val = 0;
done:
*dest = val;
}複製**
迴圈結構
c 語言中有do-while
、while
和for
三種迴圈結構,它們的通用形式一般都長下面那樣。
// do-while
do body-statement
while
(test-expr)
;
// while
while(test-expr)
body-statement
// for
for(init-expr; test-expr; update-expr)
body-statement
複製**
do-while
的特點是body-statement
一定會執行一次,所以我們可以將do-while
翻譯成下面的控制流形式,很容易就能聯想到它的彙編形式。
loop:
body-statement;
t = test-expr;
if(t)
複製**
while
迴圈我們給出兩種形式的控制流,其中一種包含do-while
形式,如下所示。
// 第一種形式
t = test-expr;
if(!t)
do body-statement;
while(test-expr);
done:
// 第二種形式
goto test;
loop:
body-statement;
test:
t = test-expr;
if(t)
複製**
面試的時候,有的面試官會問你for
迴圈的執行順序,現在深入理解了三種迴圈的機制,再也不怕面試官啦。for
迴圈可以轉換成如下的while
形式。
init-expr;
while(test-expr)
複製**
有了這種形式的for
迴圈,我們只需要將其中的while
部分再翻譯一下就好了,前文給出了兩種while
翻譯的方式,而具體採用哪種方式,取決於編譯器優化的等級。
總結計算機就是用那麼幾條簡簡單單的指令就完成了各種複雜的操作,不得不折服於計算機科學家們的魅力。現在人工智慧被炒的很火熱,然後人是事件、情感驅動的,而計算機是控制流驅動的,所以從架構上就決定了,馮諾依曼體系計算機實現的都是弱人工智慧。
順序 條件 迴圈語句的底層解釋
順序結構 資料傳送指令 我們都清楚,絕大多數編譯器都把組合語言作為中間語言,把組合語言程式變成可執行的二進位制檔案早就解決了,所以現在的高階語言基本上只需要把自己翻譯成組合語言就可以了。彙編指令總共只有那麼多,大多數指令都是對資料進行操作,比如常見的資料傳送指令mov。不難理解,被運算元據無非有三種...
條件語句 迴圈語句
1 switch case switch中的比較是用的equals,而不是 switch中只能使用byte short int char string 列舉型別。不能使用long flaot double 各個case標籤不必連續 也不按特定順序排列,default標籤可位於switch case結...
IE 條件解釋的語句
這裡是乙個演示條件注釋如何工作的簡單示例。you are not using internet explorer.p 有兩種 條件注釋 下層顯示 downlevel revealed 和下層隱藏 downlevel hidden 每種注釋的基本句法如下表所示。第一條展示的是基本的 html 注釋,被...