目錄
- 基礎知識介紹
- 1. 暫存器的種類與功能
- 2. 常用匯編指令
- 3. 記憶體模型
- 演示函式堆疊幀的創建銷毀程序
- 1. 為main()函式開辟堆疊幀
- 2. 在main()函式中創建變數
- 3. 呼叫Add()函式前的準備
- 4. 為Add()函式開辟堆疊幀
- 5. 在Add()函式中創建變數并運算
- 6. Add()堆疊幀的銷毀
- 7. 回傳main()函式堆疊幀
- 總結
電腦中的任何指令都是在CPU上的運行的,但是CPU本身只負責運算不負責存盤,資料一般都是存盤在記憶體和暫存器(儲存最常用的資料),
想要理解函式堆疊幀的創建和銷毀,首先必須了解三個知識點:暫存器、常用匯編指令及記憶體模型,
基礎知識介紹
1. 暫存器的種類與功能
| 暫存器名稱 | 功能 |
|---|---|
| eax | 累加暫存器,相對于其他暫存器,在運算方面比較常用, |
| ebx | 基地址暫存器,在記憶體尋址時存放基地址, |
| ecx | 計數暫存器,用于回圈操作,比如重復的字符存盤操作,或者數字統計, |
| edx | 作為EAX的溢位暫存器,總是被用來放整數除法產生的余數, |
| esi | 源變址暫存器,主要用于存放存盤單元在段內的偏移量,通常在記憶體操作指令中作為“源地址指標”使用, |
| edi | 目的變址暫存器,主要用于存放存盤單元在段內的偏移量, |
| eip | 控制暫存器,存盤CPU下次所執行的指令地址(存放指令偏移地址), |
| esp | 堆疊頂指標,堆疊的頂部是地址小的區域,壓入堆疊的資料越多,esp也就越來越小,在32位平臺上,esp每次減少4位元組,堆疊指標暫存器(extended stack pointer),其記憶體放著一個指標,該指標永遠指向系統堆疊最上面一個堆疊幀的堆疊頂,是CPU機制決定的,push、pop指令會自動調整esp的值, |
| ebp | 基址指標,指堆疊的堆疊底指標,基址指標暫存器(extended base pointer),一般與esp配合使用,可以存取某時刻的esp,這個時刻就是進入一個函式內后,CPU會將esp的值賦給ebp,此時就可以通過ebp對堆疊進行操作,比如獲取函式引數,區域變數等,其記憶體放著一個指標,該指標永遠指向系統堆疊最上面一個堆疊幀的底部, |
2. 常用匯編指令
- push指令:它首先減少
esp的值,再將源運算元復制到堆疊地址,在32位平臺上,esp每次減少4位元組,

解釋:首先esp的值減少4位元組,再將ebp的值壓入堆疊中, - pop指令:它首先把
esp指向的堆疊元素內容復制到一個運算元中,再增加esp的值,在32位平臺上,esp每次增加4位元組,

解釋:首先將esp所指地址處的值賦給edi,再將esp的值減少4位元組,
- mov指令:用于將一個資料從源地址傳送到目標地址,源操作地址的內容不變,

解釋:將esp值賦給ebp,這里并不是將esp所指向的記憶體空間的值賦給 ebp
-
sub指令:減操作指令,從暫存器中減去<shifter_operand>表示的數值,并將結果保存到目標暫存器中,

解釋:esp-0E4h位元組的結果保存在esp中, -
lea指令:是“load effective address”的縮寫,簡單的說,lea指令可以用來將一個記憶體地址直接賦給目的運算元,

解釋:將ebp-0E4h的值直接賦給edi,而不是把ebp-0E4h記憶體地址里的資料賦給eax, -
rep指令:重復前綴指令,英文縮寫 repeat,能夠引發其后字串指令被重復,
-
stos指令:串存盤指令,英文縮寫 store string,

解釋:
上述幾條指令通常一起使用,
rep指令重復其上面的指令,ecx的值是重復的次數,每執行一次,ecx減 1,直到ecx減至0,
stos指令將eax中的值拷貝到es:[edi]指向的地址,
dword雙字 就是四個位元組,
ptrpointer縮寫 即指標
[ ]里的資料是一個地址值,這個地址指向一個雙字型資料
一次拷貝雙字(4個位元組)的資料到目的地址,
es:[edi]指向目的串
解釋:合起來的意思就是,將堆疊上從ebp-0E4h開始的位置,向高地址方向的記憶體賦值 0CCCCCCCCh,重復 39h 次,每次賦值雙字(四位元組的空間), -
call指令:將程式下一條指令的位置的IP壓入堆疊中,并轉移到呼叫的子程序,

解釋:將下一條指令的IP(00BF1A30)壓入堆疊中,并移動到呼叫的子程式, -
jmp指令:無條件跳轉指令,

解釋:無條件跳轉到IP為(0BF3BE0H)的位置, -
add指令:用于將兩個運算子相加,并將結果寫入第一個運算子,

解釋:給esp加8,也就是esp向高地址方向移動 8位元組 ,相當于pop操作后的指標變化, -
ret指令:用于終止當前函式的執行,將運行權交還給上層函式,也就是,當前函式的幀將被回收,

解釋:執行這條命令之后,就自動回傳剛才call指令的下一行,

3. 記憶體模型

從邏輯上講,堆疊幀就是一個函式執行的環境:函式引數、函式的區域變數、函式執行完后回傳到哪里等等,
首先應該明白,堆疊是從高地址向低地址延伸的,每個函式的每次呼叫,都有它自己獨立的一個堆疊幀,這個堆疊幀中維持著所需要的各種資訊,暫存器ebp指向當前的堆疊幀的底部(高地址),暫存器esp指向當前的堆疊幀的頂部(低地址),
這次演示所使用的環境是windows 10、編譯環境 vs2013(debug、Win32),
在不同的編譯器下,函式呼叫程序中堆疊幀的創建是略有差異的,具體細節取決于編譯器的實作,
友情提示:
不要使用太高級的編譯器,越高級的編譯器,越不容易學習和觀察,
演示函式堆疊幀的創建銷毀程序
首先來看下這次演示使用的代碼:
// 為了能夠觀察全部的細節,所以把代碼拆的足夠細,
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
}
按下F10,在視圖中打開呼叫堆疊視窗,我們發現main()函式被呼叫了,
但是main()函式被誰呼叫了呢?
當我們接著除錯到return 0;之后,再按F10,我們發現程式跳轉到了呼叫main()函式的函式內

原來main()函式是被__tmainCRTStartup函式呼叫的,而 __tmainCRTStartup又是被mainCRTStartup呼叫的,
接下來分步驟演示函式堆疊幀的創建和銷毀的程序,
1. 為main()函式開辟堆疊幀


2. 在main()函式中創建變數


3. 呼叫Add()函式前的準備


4. 為Add()函式開辟堆疊幀


5. 在Add()函式中創建變數并運算


6. Add()堆疊幀的銷毀


7. 回傳main()函式堆疊幀

可以看到這里回傳到了第3步(3. 呼叫Add()函式前的準備),最后指令call的下一條指令,
之后的程序還很復雜,這里就不詳細展示了,
有興趣的鐵鐵們可以自己研究研究,
總結
看到這里,想必以前學習中的許多困惑已經有了答案吧,
比如:
- 區域變數是怎么創建的?
- 為什么區域變數的值是隨機值?
- 函式是怎么傳參的?傳參的順序是怎樣的?
- 形參和實參是什么關系?
- 函式呼叫是怎么做的?
- 函式呼叫是結束后怎么回傳的?
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/293661.html
標籤:其他
上一篇:計算機網路原理(基礎)
下一篇:嵌入式單片機產品開發設計框架
