栈中函数调用原理_详解
?
??????????? 函數(shù)調(diào)用是程序設(shè)計(jì)中的重要環(huán)節(jié),本文就函數(shù)調(diào)用的過程進(jìn)行分析。一、eip、ebp、esp介紹
?EIP,EBP,ESP都是系統(tǒng)的寄存器,里面存儲的是些地址,我們系統(tǒng)中棧的實(shí)現(xiàn)上離不開他們?nèi)齻€。?我知道棧的數(shù)據(jù)結(jié)構(gòu)主要特點(diǎn)是?后進(jìn)先處。它還有兩個作用:?1.棧是用來存儲臨時(shí)變量,函數(shù)傳遞的中間結(jié)果。?2.操作系統(tǒng)維護(hù)的,對于程序員是透明的。
下面我們就通過一個小例子說說棧的原理。
先寫個小程序:
| 1 2 3 4 5 6 7 8 9 | void fun(void){printf("helloworld");}void main(void){fun()printf("函數(shù)調(diào)用結(jié)束");} | 
來自CODE的代碼片 snippet_file_0.txt
當(dāng)程序進(jìn)行函數(shù)調(diào)用的時(shí)候,我們經(jīng)常說的是先將函數(shù)壓棧,當(dāng)函數(shù)調(diào)用結(jié)束后,再出棧。這一切的工作都是系統(tǒng)幫我們自動完成的。但在完成的過程中,系統(tǒng)會用到下面三種寄存器:EIP、ESP、EBP。
當(dāng)調(diào)用fun函數(shù)開始時(shí),三者的作用。
二、堆和棧
首先要清楚的是程序?qū)?nèi)存的使用分為以下幾個區(qū):
典型的內(nèi)存區(qū)域分配如圖所示:
 
其次是堆和棧的申請方式:棧由系統(tǒng)自動分配,速度較快,在windows下棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存區(qū)域,大小是2MB。堆需要程序員自己申請,并指明大小,速度比較慢。在C中用malloc,C++中用new。另外,堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域,堆的大小受限于計(jì)算機(jī)的虛擬內(nèi)存。因此堆空間獲取和使用比較靈活,可用空間較大。?
三、棧幀結(jié)構(gòu)和函數(shù)調(diào)用過程
首先應(yīng)該明白,棧是從高地址向低地址延伸的。每個函數(shù)的每次調(diào)用,都有它自己獨(dú)立的一個棧幀,這個棧幀中維持著所需要的各種信息。寄存器ebp指向當(dāng)前的棧幀的底部(高地址),寄存器esp指向當(dāng)前的棧幀的頂部(地址地)。下圖為典型的存取器安排,觀察棧在其中的位置
 
入棧操作:push eax; 等價(jià)于 esp=esp-4,eax->[esp];如下圖
 
出棧操作:pop eax; 等價(jià)于 [esp]->eax,esp=esp+4;如下圖
 
我們來看下面這個C程序在執(zhí)行過程中,棧的變化情況
| 1 2 3 4 5 6 7 8 9 10 11 | void func(int m, int n) {int a, b;a = m;b = n;}main() {...func(m, n);L: 下一條語句...} | 
來自CODE的代碼片 snippet_file_0.txt
在main調(diào)用func函數(shù)前,棧的情況,也就是說main的棧幀:
 
從低地址esp到高地址ebp的這塊區(qū)域,就是當(dāng)前main函數(shù)的棧幀。當(dāng)main中調(diào)用func時(shí),寫成匯編大致是:
push m
push n; 兩個參數(shù)壓入棧
call func; 調(diào)用func,將返回地址填入棧,并跳轉(zhuǎn)到func
 
當(dāng)跳轉(zhuǎn)到了func,來看看func的匯編大致的樣子:
__func:
push ebp; 這個很重要,因?yàn)楝F(xiàn)在到了一個新的函數(shù),也就是說要有自己的棧幀了,那么,必須把上面的函數(shù)main的棧幀底部保存起來,棧頂是不用保存的,因?yàn)樯弦粋€棧幀的頂部講會是func的棧幀底部。(兩棧幀相鄰的)
?? ? ??mov ebp, esp; 上一棧幀的頂部,就是這個棧幀的底部;暫時(shí)先看現(xiàn)在的棧的情況
 
到這里,新的棧幀開始了
sub esp, 8; ?int a, b 這里聲明了兩個int,所以esp減小8個字節(jié)來為a,b分配空間
mov dword ptr [esp+4],[ebp+12]; ? a=m
mov dword ptr [esp], [ebp+8];b=n ? ? ? ??
這樣,棧的情況變?yōu)?#xff1a;
 
ret 8 ; ?返回,然后8是什么意思呢,就是參數(shù)占用的字節(jié)數(shù),當(dāng)返回后,esp-8,釋放參數(shù)m,n的空間。由此可見,通過ebp,能夠很容易定位到上面的參數(shù)。當(dāng)從func函數(shù)返回時(shí),首先esp移動到棧幀底部(即釋放局部變量),然后把上一個函數(shù)的棧幀底部指針彈出到ebp,再彈出返回地址到cs:ip上,esp繼續(xù)移動劃過參數(shù),這樣,ebp,esp就回到了調(diào)用函數(shù)前的狀態(tài),即現(xiàn)在恢復(fù)了原來的main的棧幀。
總結(jié)
以上是生活随笔為你收集整理的栈中函数调用原理_详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: P2P-挑战和机遇
 - 下一篇: C++ 复制字符串/字符数组