型參在**開闢記憶體?
型參的入棧順序?
函式返回值怎麼帶出來?
函式的返回值為什麼會回退到棧裡?
函式呼叫結束為什麼會沿著呼叫點繼續執行?
我們先來了解一下堆與棧是怎樣的一種存在
什麼是棧?
棧用於維護函式呼叫的上下文,離開棧,函式就沒有辦法實現。棧通常在使用者空間的最高位址處分配,通常有數兆位元組大小。
棧在程式執行中具有舉足輕重的地位。最重要的是,棧儲存了乙個函式呼叫所需要的的維護資訊,這常常被稱為堆幀棧或者活動記錄。堆疊幀一般包括以下幾個內容:
什麼是堆?
堆是用來容納應用程式動態分配記憶體的記憶體區域,當程式使用malloc或new分配記憶體時,得到的記憶體來自堆裡,堆也可能沒有固定統一的儲存區域。堆一般比棧大很多,可能有幾十數百兆自己的容量。
認識函式堆疊呼叫的一些簡單指令
mov 移值指令
lea 移位址
push 用棧
pop 出棧
call
jump到被呼叫方函式
認識暫存器的存在
暫存器是cpu內部的元件,暫存器擁有非常高的讀寫速度,所以在暫存器之間的資料傳送非常快。
暫存器的用途:
1.可將暫存器內的資料執行算術及邏輯運算。
2.存於暫存器內的位址可用來指向記憶體的某個位置,即定址。
3.可以用來讀寫資料到電腦的周邊裝置。
eax 是"累加器"(accumulator), 它是很多加法乘法指令的預設暫存器。
add eax,4; eax+=4
sub eax,4; eax-=4
ebx 是"基位址"(base)暫存器, 在記憶體定址時存放基位址。
ecx 是計數器(counter), 是重複(rep)字首指令和loop指令的內定計數器。
edx 總是被用來放整數除法產生的餘數。
ebp 棧底指標暫存器
esp 棧頂指標暫存器
pc 下一行指令暫存器
在i386中,乙個函式的活動記錄用ebp(棧底指標暫存器)和esp(棧頂指標暫存器)這兩個暫存器劃定範圍。esp暫存器始終指向棧的頂部,同時也就指向了當前函式的活動記錄的頂部。而相對的,ebp的暫存器指向了函式活動記錄的乙個固定範圍。
函式堆疊呼叫的實質
用下邊的簡單**舉例了解一下
int add(int a,int b)
int main()
型參開闢記憶體:呼叫方(如下**)
main 函式棧布局:
被呼叫方:(如下**)
add函式棧幀開闢:
0119291d mov esp,ebp //釋放add函式的棧幀空間
0119291f pop ebp
01192920 ret
函式呼叫過程開闢型參的記憶體並初始化為0xcccccccc
壓入呼叫方棧底指標的值
開闢區域性變數所需要的棧空間並初始化
函式呼叫完成後的清棧
清理棧開闢的區域性變數
pop ebp(棧底指標暫存器) ebp回退到呼叫方棧底
ret(pop pc)
清棧的意義:告訴暫存器記憶體可以再次被分配。
函式的呼叫約定:
(1)thiscall: 類成員方法的呼叫約定,this指標存放於cx暫存器,引數從右到左壓。
內建型別的呼叫約定(calling convention):
(2)_cdecl:c標準的呼叫。型參由呼叫方開闢,被呼叫方清理。每乙個呼叫它的函式都包含清空堆疊的**,所以產生的可執行檔案大小會比呼叫_stdcall函式的大。
(3)__stdcall:windows標準的呼叫約定,型參由呼叫方開闢,被呼叫方清理。
(4)__fastcall:快速呼叫約定,它是通過暫存器來傳送引數的(實際上,它用ecx和edx傳送前兩個雙字(dword)或更小的引數,剩下的引數仍舊自右向左壓棧傳送,被呼叫的函式在返回前清理傳送引數的記憶體棧)。
約定的意義:
1)約定了函式符號的生成。(不同的函式約定,函式符號的生成不一樣)
2)約定了函式引數的壓棧順序。
3)約定了型參的開闢和清理方式。
接下來我們解決文章開頭的問題???
1、型參在**開闢記憶體?
型參在棧上開闢記憶體,由呼叫方開闢。
2、型參的入棧順序?
型參從右向左入棧,如果從左至右的話,不知道實參的傳遞個數。
3、函式返回值怎麼帶出來?
函式值存放到暫存器中,由暫存器帶回
eax帶回返回值:
(1)0《返回值<=4 由eax帶回
(2)4《返回值<=8 由eax edx 兩個暫存器帶回
(3)8《返回值 由臨時量帶回(非類型別)
非類型別的返回方式:
4、函式的返回值為什麼會回退到棧裡?
因為壓棧壓的是呼叫方的棧底指標
5、函式執行完成後怎麼控制沿用點繼續執行?
通過call指令繼續。
call指令的作用:
(2)jump到被呼叫方函式
tips:
實參與型參:
int add(int a,int b)//型參,用來接收呼叫該方法時傳遞的引數
int main()
函式定義與函式說明:
int add(int a,int b) //求兩個整型數之和
int add(int a,int b)//實現函式
不需要宣告int add(int a,int b)int main()
需要宣告int add(int a,int b);//函式宣告,需要加分號int main()//被呼叫的函式在呼叫之後
int add(int a,int b)
函式的呼叫堆疊
在學習c 的過程中,有面向過程和物件導向兩種程式設計方式。對於面向過程來說,函式的書寫是最基本的,所以了解函式的呼叫過程和函式呼叫的底層原理也是必須要會的事情。那麼函式棧幀的開闢和回退是怎麼進行的呢?下面我們先用乙個簡單的例子,通過乙個模擬模型和反彙編來了解一下函式堆疊的呼叫。題外話 1 說到彙編我...
函式的堆疊呼叫
今天我們來介紹一下函式的堆疊呼叫過程,首先,我們以乙個簡單的例子進行說明 includeint sum int a,int b int main 將上面 轉到反彙編 sum函式 int sum int a,int b 00e21401 pop edi 出棧 00e21402 pop esi 00e2...
函式呼叫堆疊
一 函式呼叫堆疊 認真體會每一行指令位址!include intsum int a,int b mov ebp,esp 讓esp回退到ebp的位置,回退棧幀的過程中,沒有對棧幀中的值進行清0的操作 pop ebp 出棧並把出棧的值賦給ebp int main 下圖為上面示例函式,程式在sum函式中,...