文章目錄
- 前言
- 預備知識
- 示例代碼
- 對應的反匯編代碼
- main()函式的呼叫堆疊
- Add()函式的呼叫堆疊
- 堆疊區呼叫圖示
前言
本文從記憶體層面上主要講解函式的以下幾個問題
- 區域變數是怎樣創建的
- 為什么區域變數的值是隨機值
- 函式是怎樣傳參的,傳參的順序是怎樣的
- 形參和實參之間是什么關系
- 函式呼叫是怎么做的
- 函式呼叫結果如何回傳
本文也有助于理解程式是如何被計算機執行的
但是本文寫的很亂,待博主理解得更清晰后會再次整理
預備知識
暫存器:esp(堆疊頂指標,指向函式堆疊幀的頂部)和ebp(堆疊底指標,指向函式堆疊幀的底部)用于維護函式堆疊幀,
壓堆疊(push):從堆疊頂壓一個位元組,esp向低地址移動一位,
出堆疊(pop):從堆疊頂洗掉一個位元組,esp向高地址移動一位,
dword代表四個位元組,
堆疊區的使用規則是先使用高地址,再使用低地址,
示例代碼
本文以如下代碼為例子,粗略講解它在計算機是如何被執行的
#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;
}
對應的反匯編代碼
main()函式的呼叫堆疊

Add()函式的呼叫堆疊

堆疊區呼叫圖示
- 進入main()之前堆疊區是這樣的

- 第一潭訓編指令
push ebp
在原來的堆疊頂上開辟一個位元組的空間,存放ebp的值(這個ebp的值是呼叫main()的函式的ebp,為函式呼叫結束回傳做鋪墊),指向堆疊頂指標也跟著+1

- 第二條指令

把esp的值賦給ebp,此時兩者指向同一個位置,執行流離開呼叫main()的那個函式

- 接下來

esp的值減去0E4h(十六進制,對應十進制的228),esp指向一個更低的地址
這就為main()分配好了堆疊幀空間

- 接下來三條push指令在堆疊頂再開辟三個位元組的空間,分別放ebx、esi、edi,
這三個空間是干什么的本文不涉及,但是每次都用函式都會出現


- 接下來的四條指令是為開辟的空間初始化,意思是先把ebp-24h的值加載到edi中,把9賦值給ecx,0CCCCCCCCh賦值給eax,最后把從ebp指向的后一個記憶體(往低地址方向)直到edi指向的記憶體賦值為eax的內容(初始化完后edi的值會變成ebp的值),這點可以從vs2019的記憶體視窗看到,



-
以上便是main()的堆疊幀呼叫
下面是我們寫的C語言代碼對應的匯編代碼
以下幾條指令完成a、b、c的創建和初始化,分別意思是把0Ah(十進制下的10)賦值給ebp-8所指向的記憶體(也就是變數a的記憶體),把14h(十進制下的20)賦值給ebp-14h所指向的記憶體(也就是b的記憶體),把0賦值給ebp-20h所指向的記憶體(也就是c的記憶體)


-
接下來將要呼叫Add函式,而在呼叫Add函式之前,需要先傳參
以下四條指令完成了傳參這個動作:
先把ebp-14h(也就是&b)指向的內容賦值給暫存器eax,再在堆疊頂開辟一位元組的空間,放的是eax的值;再把ebp-8(也就是&a)指向的空間賦值給暫存器ecx,再在堆疊頂開辟一位元組的空間,放的是ecx的值

-
接下來一條call指令使得執行流跳轉到Add的匯編代碼,并且在堆疊頂再開辟一條空間,放的是存放call的下一條指令的地址(為了函式呼叫結束后能夠將繼續執行后面的代碼)


-
跳轉到Add函式后,可以發現前面的匯編代碼和main()函式幾乎是一樣的,只是創建的堆疊幀空間大小不同

-
第一句指令先再堆疊頂開辟一位元組的空間,放ebp的值(為了回傳main()做鋪墊);第二句mov把esp的值賦值給ebp,這樣,程式的執行流便離開了main()

-
接下來一條指令把esp的值減去0CCh,這樣便為Add()創建了堆疊幀空間

-
接下來三個push

-
接下來初始化了三個位元組為CCCCCCCC


- 接下來創建Add()中的臨時變數z,ebp-8對應的位置的記憶體就是z,被初始化為0


- 接下來三條指令執行z=x+y;
先把ebp+8所指的記憶體(就是形參x)存放的值賦值給暫存器eax,再把ebp+0Ch所指的記憶體(就是形參y)存放的值加到暫存器eax上,最后再把eax的值賦值給ebp-8指向的記憶體(也就是z)

- 接下來執行return z;
先把ebp-8處的值賦值給暫存器eax,再依次彈出堆疊頂的三個位元組并把內容依次賦值給暫存器edi、esi、ebx

- 接著esp+0CCh,為Add開辟的堆疊空間被釋放

- add下面的三條指令暫且不管
指令pop ebp彈出堆疊頂的一個記憶體單元并把該記憶體單元內的值賦值給ebp,而這個值就是呼叫Add()之前main()的ebp的值,因此執行流便回到了main()


-
ret執行后

-
由于先前保存了Add()下一條指令的地址,這條指令既是為了釋放形參變數的空間,也使得程式繼續執行呼叫Add()之后的代碼

esp+8使得形參x、y的空間被釋放

-
下面一條指令把eax的值(就是z保存的值)賦值給ebp-20h處的記憶體(就是c),至此便完成了c=Add(a,b);這條代碼

-
接著是呼叫庫函式printf(),
最后return 0回傳上一級呼叫main()的函式,由于是基于同樣的原理,就不再重復了,
希望本文對你對程式是如何執行的有了更深一層的認識,另外,由于作者水平非常有限,如果有錯誤,還請讀者能幫我指出,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292393.html
標籤:其他
上一篇:淺談函式堆疊幀
