真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

函數(shù)的調(diào)用過程(棧幀)

    

蠡縣網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、自適應(yīng)網(wǎng)站建設(shè)等網(wǎng)站項目制作,到程序開發(fā),運營維護。成都創(chuàng)新互聯(lián)公司自2013年創(chuàng)立以來到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)公司。

1、什么是棧幀?

棧幀也叫過程活動記錄,是編譯器用來實現(xiàn)函數(shù)調(diào)用過程的一種數(shù)據(jù)結(jié)構(gòu)。C語言中,每個棧幀對應(yīng)著一個未運行完的函數(shù)。從邏輯上講,棧幀就是一個函數(shù)執(zhí)行的環(huán)境:函數(shù)調(diào)用框架、函數(shù)參數(shù)、函數(shù)的局部變量、函數(shù)執(zhí)行完后返回到哪里等等。棧是從高地址向低地址延伸的。每個函數(shù)的每次調(diào)用,都有它自己獨立的一個棧幀,這個棧幀中維持著所需要的各種信息。寄存器ebp指向當(dāng)前的棧幀的底部(高地址),寄存器esp指向當(dāng)前的棧幀的頂部(低地址)。

2、Add()函數(shù)的調(diào)用過程

我們以Add()函數(shù)為例深入的研究一下函數(shù)的調(diào)用過程。
先看一段簡單的代碼:

函數(shù)的調(diào)用過程(棧幀)

 1 #include  2 int Add(int x, int y) 3 { 4 int z = 0; 5 z = x + y; 6 return z; 7 } 8 int main() 9 {10 int a = 10;11 int b = 20;12 int ret = Add(a, b) ;13 printf("ret = %d\n", ret) ;14 return 0;15 }

函數(shù)的調(diào)用過程(棧幀)

當(dāng)講程序調(diào)試的時候, 查看【調(diào)用堆?!浚ò碏10進入調(diào)試-窗口-調(diào)用堆棧,或按快捷鍵ctrl+alt+C) ,用VS2015調(diào)試 如下圖:
函數(shù)的調(diào)用過程(棧幀)

如果用版本更老的,或其他如VC6.0等編輯器則可以看到更多信息,VS2008調(diào)試如圖:函數(shù)的調(diào)用過程(棧幀)

我們發(fā)現(xiàn)其實main函數(shù)在 __tmai nCRTStartup 函數(shù)中調(diào)用的,而 __tmai nCRTStartup 函數(shù)是在 mai nCRTStartup 被調(diào)用的。我們知道每一次函數(shù)調(diào)用都是一個過程。這個過程我們通常稱之為: 函數(shù)的調(diào)用過程。這個過程要為函數(shù)開辟??臻g, 用于本次函數(shù)的調(diào)用中臨時變量的保存、 現(xiàn)場保護。 這塊??臻g我們稱之為函數(shù)棧幀。
而棧幀的維護我們必須了解ebp和esp兩個寄存器。 在函數(shù)調(diào)用的過程中這兩個寄存器存放了維護這個棧的棧底和棧頂指針。比如:調(diào)用main函數(shù), 我們?yōu)閙ain函數(shù)分配棧幀空間, 那么棧幀維護如下:
函數(shù)的調(diào)用過程(棧幀)ebp存放了指向函數(shù)棧幀棧底的地址。esp存放了指向函數(shù)棧幀棧頂?shù)牡刂贰?br />注意:ebp指向當(dāng)前位于系統(tǒng)棧最上邊一個棧幀的底部,而不是系統(tǒng)棧的底部。嚴(yán)格說來,“棧幀底部”和“棧底”是不同的概念;ESP所指的棧幀頂部和系統(tǒng)棧的頂部是同一個位置。

1 . 從main函數(shù)的地方開始, 要展開main函數(shù)的調(diào)用就得為main函數(shù)創(chuàng)建棧幀, 那我們先來看main函數(shù)棧幀的創(chuàng)建。轉(zhuǎn)到反匯編可以更清晰的看到過程:函數(shù)的調(diào)用過程(棧幀)

過程分析:

a.首先mainCRTStartup(),__mainCRTStartup()函數(shù)的調(diào)用,調(diào)main()函數(shù);

b.將ebp壓棧處理,保存指向棧底的ebp的地址(方便函數(shù)返回之后的現(xiàn)場恢復(fù)),此時esp指向新的棧頂位置;

c.將esp的值賦給ebp,產(chǎn)生新的ebp;

d.給esp減去一個16進制數(shù)0E4H(為main函數(shù)預(yù)開辟空間);

e.push ebx、esi、edi;

f.lea指令,加載有效地址;

g.初始化預(yù)開辟的空間為0xcccccccc;

h.創(chuàng)建變量a與b。

2. 接下來是Add函數(shù)的調(diào)用。

參數(shù)傳遞過程:函數(shù)的調(diào)用過程(棧幀)
 過程分析:

a.將b存入寄存器eax,再將將eax壓棧;(傳參過程,從左向右傳遞)

b.將a存入寄存器ecx,再將將ecx壓棧;

c.call指令的調(diào)用,先要壓棧call指令下一條指令的 地址,然后跳轉(zhuǎn)(push+jmp)到Add()函數(shù)的地方(__cdecl調(diào)用約定)。執(zhí)行call指令的時候按F11 , 來到了這里。
函數(shù)的調(diào)用過程(棧幀)再按F11 就進入Add函數(shù)的執(zhí)行代碼處。Add函數(shù)棧幀的創(chuàng)建:函數(shù)的調(diào)用過程(棧幀)

過程分析:

a.首先將main()函數(shù)ebp壓棧處理,保存指向main()函數(shù)棧幀底部的ebp的地址(方便函數(shù)返回之后的現(xiàn)場恢復(fù)),此時esp指向新的棧頂位置;

b.將esp的值賦給ebp,產(chǎn)生新的ebp,即Add()函數(shù)棧幀的ebp;

c.給esp減去一個16進制數(shù)0E4H(為Add()函數(shù)預(yù)開辟空間);

d.push ebx、esi、edi;

e.lea指令,加載有效地址;

f.初始化預(yù)開辟的空間為0xcccccccc;

g.創(chuàng)建變量z;

h.獲取形參的a和b再相加,將結(jié)果存儲到z中;

i.將結(jié)果存儲到eax寄存器,通過寄存器帶回函數(shù)的返回值。
剩下的就是是函數(shù)返回部分:函數(shù)的調(diào)用過程(棧幀)

過程分析:

a.pop3次,edi、esi、ebx依次出棧,esp 會向下移動;

b.將ebp賦給esp,使esp指向ebp指向的地方

c.ebp 出棧,將出棧的內(nèi)容給ebp(即main()函數(shù)ebp),回到main()函數(shù)的棧幀;

d.ret 指令,出棧一次,并將出棧的內(nèi)容當(dāng)做地址,并跳轉(zhuǎn)到該地址處(pop+jmp)。

函數(shù)的調(diào)用過程(棧幀)

注: 棧幀這部分內(nèi)容在不同的編譯器上實現(xiàn)存在差異, 但是思想都是一致的。

棧幀的一般總結(jié):

1. 堆棧是C語言程序運行時必須的一個記錄調(diào)用路徑和參數(shù)的空間:
函數(shù)調(diào)用框架;
傳遞參數(shù);
保存返回地址;
提供局部變量空間;
等等。
以x86體系結(jié)構(gòu)為例
2. 堆棧寄存器和堆棧操作
 堆棧相關(guān)的寄存器
esp,堆棧指針(stack pointer)
ebp,基址指針(base pointer)
堆棧操作
push 棧頂?shù)刂窚p少4個字節(jié)(32位)
pop 棧頂?shù)刂吩黾?個字節(jié)
ebp在C語言中用作記錄當(dāng)前函數(shù)調(diào)用基址
3. 利用堆棧實現(xiàn)函數(shù)調(diào)用和返回
其他關(guān)鍵寄存器
cs : eip:總是指向下一條的指令地址
● 順序執(zhí)行:總是指向地址連續(xù)的下一條指令
● 跳轉(zhuǎn)/分支:執(zhí)行這樣的指令的時候, cs : eip的值會根據(jù)程序需要被修改
● call:將當(dāng)前cs : eip的值壓入棧頂, cs : eip指向被調(diào)用函數(shù)的入口地址
● ret:從棧頂彈出原來保存在這里的cs : eip的值,放在cs : eip中
● 發(fā)生中斷時???
4. 函數(shù)堆??蚣艿男纬?/strong>

call xxx
執(zhí)行call之前;
執(zhí)行call時,cs:eip原來的值指向call下一條指令,該值被保存到棧頂,然后cs:eip的值指向xxx的入口地址
進入xxx
第一條指令:pushl %ebp
第二條指令:movl %esp,%ebp
函數(shù)體中的常規(guī)操作,壓棧,出棧等
退出xxx
movl %ebp,%esp
popl %ebp
ret

函數(shù)的調(diào)用過程(棧幀)

5. 堆和棧的關(guān)系
我們平時說的堆棧其實是指棧,而實際上堆和棧是兩種不同的內(nèi)存分配。簡單羅列如下各方面的異同點。
1).堆需要用戶在程序中顯式申請,棧不用,由系統(tǒng)自動完成。申請/釋放堆內(nèi)存的API,在C中是malloc/free,在C++中是new/delete。申請與釋放一定要配對使用,否則會造成內(nèi)存泄漏(memory leak),久而久之系統(tǒng)就無內(nèi)存可用了,出現(xiàn)OOM(Out Of Memory)錯誤。一般在return/exit或break/continue等語句時容易忘記釋放內(nèi)存,所以檢查內(nèi)存泄漏的代碼時要關(guān)注這些語句,看它們前面是否有必要的釋放語句free/delete。
2).堆的空間比較大,棧比較小。所以申請大的內(nèi)存一般在堆中申請;棧上不要有較大的內(nèi)存使用,比如大的靜態(tài)數(shù)組;而且除非算法必要,否則一般不要使用較深的迭代函數(shù)調(diào)用,那樣棧消耗內(nèi)存會隨著迭代次數(shù)的增加飛漲。
3).關(guān)于生命周期。棧較短,隨著函數(shù)退出或返回,本函數(shù)的棧就完成了使用;堆就要看什么時候釋放,生命周期就什么時候結(jié)束。
函數(shù)的調(diào)用過程(棧幀)我們發(fā)現(xiàn)解析Coredump還是跟棧的關(guān)系相對緊密,跟堆的關(guān)系是有一種產(chǎn)
生Coredump的原因是訪問堆內(nèi)存出錯。

為什么研究棧幀?看一個題目 :
在VC6.0環(huán)境中, 下面代碼的結(jié)果是什么?

函數(shù)的調(diào)用過程(棧幀)

 1 #include  2 void fun() 3 { 4 int tmp = 10; 5 int *p = (int *) (*(&tmp+1) ) ; 6 *(p-1) = 20; 7 } 8 int main() 9 {10 int a =0;11 fun() ;12 printf("a = %d\n", a) ;13 return 0;14 }

函數(shù)的調(diào)用過程(棧幀)

事實上在不同平臺下這段代碼有不同的輸出,可自行驗證。

    


當(dāng)前名稱:函數(shù)的調(diào)用過程(棧幀)
網(wǎng)頁網(wǎng)址:http://weahome.cn/article/jjoodd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部