C語言函數(shù)棧幀的創(chuàng)建和銷毀詳解
寫在前面
我們知道,每一次函數(shù)調(diào)用都需要在棧區(qū)上為其開辟一塊空間,這塊空間就叫做這個(gè)函數(shù)的棧幀。
而棧是從高地址向低地址延伸的。每個(gè)函數(shù)的每次調(diào)用,都有它自己獨(dú)立的一個(gè)棧幀,這個(gè)棧幀中維持著所需要的各種信息。寄存器ebp指向當(dāng)前的棧幀的底部(高地址),寄存器esp指向當(dāng)前的棧幀的頂部(低地址)。
這樣我們就了解了寄存器ebp和寄存器esp中存放的是地址,這兩個(gè)地址是用來維護(hù)函數(shù)棧幀的。比如:調(diào)用main函數(shù), 我們?yōu)閙ain函數(shù)分配棧幀空間, 那么棧幀維護(hù)如下:
下面我們通過一段代碼分析一下,函數(shù)棧幀創(chuàng)建和銷毀的過程:(棧幀這部分內(nèi)容在不同的編譯器上實(shí)現(xiàn)存在差異, 但是思想大致都是一致的。本文是在vs2013編譯器下實(shí)現(xiàn)的。)
#include <stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main(void) { int a = 10; int b = 20; int ret = 0; ret = Add(a, b);//計(jì)算a+b printf("%d\n", ret); return 0; }
我們?cè)谡{(diào)試過程打開調(diào)用堆棧
可以看出,main函數(shù)是在__tmainCRTStartup函數(shù)內(nèi)部被調(diào)用的,而__tmainCRTStartup函數(shù)又是在mainCRTStartup函數(shù)內(nèi)部調(diào)用的。
為了能更加清楚的看到棧幀創(chuàng)建和銷毀的過程,我們轉(zhuǎn)到上面代碼對(duì)應(yīng)的反匯編代碼:
int main(void) { 009D3F40 push ebp //將edp壓入棧幀 009D3F41 mov ebp,esp //將esp的值賦給edp 009D3F43 sub esp,0E4h //esp-0E4h 009D3F49 push ebx 009D3F4A push esi 009D3F4B push edi 009D3F4C lea edi,[ebp+FFFFFF1Ch] 009D3F52 mov ecx,39h 009D3F57 mov eax,0CCCCCCCCh 009D3F5C rep stos dword ptr es:[edi] int a = 10; 009D3F5E mov dword ptr [ebp-8],0Ah int b = 20; 009D3F65 mov dword ptr [ebp-14h],14h int ret = 0; 009D3F6C mov dword ptr [ebp-20h],0 ret = Add(a, b);//計(jì)算a+b 009D3F73 mov eax,dword ptr [ebp-14h] 009D3F76 push eax 009D3F77 mov ecx,dword ptr [ebp-8] 009D3F7A push ecx 009D3F7B call 009D11F9 009D3F80 add esp,8 009D3F83 mov dword ptr [ebp-20h],eax printf("%d\n", ret); 009D3F86 mov esi,esp 009D3F88 mov eax,dword ptr [ebp-20h] 009D3F8B push eax 009D3F8C push 9D5860h 009D3F91 call dword ptr ds:[009D9118h] 009D3F97 add esp,8 009D3F9A cmp esi,esp 009D3F9C call 009D1140 return 0; 009D3FA1 xor eax,eax } 009D3FA3 pop edi 009D3FA4 pop esi 009D3FA5 pop ebx 009D3FA6 add esp,0E4h 009D3FAC cmp ebp,esp 009D3FAE call 009D1140 009D3FB3 mov esp,ebp 009D3FB5 pop ebp 009D3FB6 ret
main函數(shù)的調(diào)用 main函數(shù)棧幀的創(chuàng)建
經(jīng)過剛才我們的理解,在準(zhǔn)備調(diào)用main函數(shù)的時(shí)候,調(diào)用main函數(shù)的那個(gè)函數(shù)的棧幀已經(jīng)開辟好了。
然后將ebp壓入棧幀,保存了指向棧底的ebp的地址,而此時(shí)esp指向新的棧頂位置;接著將esp的值賦給了ebp,產(chǎn)生了新的ebp;用esp減去一個(gè)16進(jìn)制數(shù)0E4H(這里就是為main函數(shù)預(yù)開辟空間)。緊接著三個(gè)壓棧指令,分別將ebx,esi,edi,壓入棧幀。加載完有效地址以后,將為main函數(shù)預(yù)開辟空間全部初始化為0xCCCCCCCC。最后創(chuàng)建了三個(gè)局部變量a,b,ret并進(jìn)行了初始化。
Add函數(shù)的調(diào)用
函數(shù)傳參
將b的值存入寄存器eax中,再將eax壓入棧中;將a的值存入寄存器ecx中,再將將ecx壓入棧中;這里看出參數(shù)是從右向左傳遞的。緊接著執(zhí)行call指令,這里就是調(diào)用Add函數(shù),同時(shí)將call指令的下一條指令的地址壓入棧中,然后執(zhí)行call指令的時(shí)候按F11 , 就進(jìn)入了Add函數(shù)內(nèi)部。
Add函數(shù)棧幀的創(chuàng)建
首先將main()函數(shù)的ebp壓入棧,保存指向main()函數(shù)棧幀底部的ebp的地址,此時(shí)esp指向新的棧頂位置;將esp的值賦給ebp,產(chǎn)生新的ebp,即Add()函數(shù)棧幀的ebp;給esp減去一個(gè)16進(jìn)制數(shù)0E4H,這里是為Add()函數(shù)預(yù)開辟空間;緊接著三個(gè)壓棧指令,分別將ebx,esi,edi,壓入棧幀。加載完有效地址以后,將為Add函數(shù)預(yù)開辟空間全部初始化0xCCCCCCCC。在緊接著創(chuàng)建了變量z,將形參的a和b相加的結(jié)果存儲(chǔ)到z中;最后將結(jié)果存儲(chǔ)到eax寄存器中,通過寄存器帶回了函數(shù)的返回值。
Add函數(shù)棧幀的銷毀
edi、esi、ebx依次出棧,esp 會(huì)向下移動(dòng);然后將ebp的值賦給esp,使esp指向ebp指向的地方;接著ebp 出棧,同時(shí)將出棧的內(nèi)容給ebp,此時(shí)ebp又指向了main函數(shù)棧幀的底部,最后執(zhí)行ret 指令,表示出棧一次,并跳轉(zhuǎn)到出棧的內(nèi)容的地址處,也就是call指令的下一條指令處。
main函數(shù)棧幀的銷毀
main函數(shù)棧幀的銷毀和Add函數(shù)棧幀銷毀的過程的思想都是一樣的,這里就不做多贅述了。
總結(jié)
通過上面的例子,我們知道了局部變量是如何創(chuàng)建的,知道了為什么創(chuàng)建局部變量不初始化,會(huì)導(dǎo)致里面的內(nèi)容是隨機(jī)值;對(duì)函數(shù)是如何傳參的,以及傳參順序是如何也有了較為深入的了解。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C++實(shí)現(xiàn)圖書信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)圖書信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03C++ LeetCode1796字符串中第二大數(shù)字
這篇文章主要為大家介紹了C++ LeetCode1796字符串中第二大數(shù)字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12C語言帶你學(xué)會(huì)位段相關(guān)知識(shí)
這篇文章主要介紹了什么是位段,位段的聲明和結(jié)構(gòu)是類似的,位段的成員必須是 int、unsigned int 或signed int;位段的成員名后邊有一個(gè)冒號(hào)和一個(gè)數(shù)字,本文有詳細(xì)的代碼案例,感興趣的同學(xué)可以參考閱讀2023-04-04詳解在C++中顯式默認(rèn)設(shè)置的函數(shù)和已刪除的函數(shù)的方法
這篇文章主要介紹了在C++中顯式默認(rèn)設(shè)置的函數(shù)和已刪除的函數(shù)的方法,文中講到了C++11標(biāo)準(zhǔn)中的新特性,需要的朋友可以參考下2016-01-01Qt設(shè)置窗體(QWidget)透明度的方法總結(jié)
在Qt開發(fā)中,有的時(shí)候需要為窗體設(shè)置透明度。這篇文章主要為大家介紹幾個(gè)Qt中窗體設(shè)置透明度的方法,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-11-11OpenCV使用GrabCut實(shí)現(xiàn)摳圖功能
Grabcut是基于圖割(graph cut)實(shí)現(xiàn)的圖像分割算法,它需要用戶輸入一個(gè)bounding box作為分割目標(biāo)位置,實(shí)現(xiàn)對(duì)目標(biāo)與背景的分離/分割。本文將使用GrabCut實(shí)現(xiàn)摳圖功能,需要的可以參考一下2023-02-02