溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C語言函數(shù)棧幀如何創(chuàng)建和銷毀

發(fā)布時間:2022-03-04 09:53:42 來源:億速云 閱讀:168 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“C語言函數(shù)棧幀如何創(chuàng)建和銷毀”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“C語言函數(shù)棧幀如何創(chuàng)建和銷毀”這篇文章吧。

    寫在前面

    我們知道,每一次函數(shù)調(diào)用都需要在棧區(qū)上為其開辟一塊空間,這塊空間就叫做這個函數(shù)的棧幀。

    而棧是從高地址向低地址延伸的。每個函數(shù)的每次調(diào)用,都有它自己獨立的一個棧幀,這個棧幀中維持著所需要的各種信息。寄存器ebp指向當前的棧幀的底部(高地址),寄存器esp指向當前的棧幀的頂部(低地址)。

    這樣我們就了解了寄存器ebp和寄存器esp中存放的是地址,這兩個地址是用來維護函數(shù)棧幀的。比如:調(diào)用main函數(shù), 我們?yōu)閙ain函數(shù)分配棧幀空間, 那么棧幀維護如下:

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    下面我們通過一段代碼分析一下,函數(shù)棧幀創(chuàng)建和銷毀的過程:(棧幀這部分內(nèi)容在不同的編譯器上實現(xiàn)存在差異, 但是思想大致都是一致的。本文是在vs2013編譯器下實現(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);//計算a+b
    	printf("%d\n", ret);
    	return 0;
    }

    我們在調(diào)試過程打開調(diào)用堆棧

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    可以看出,main函數(shù)是在__tmainCRTStartup函數(shù)內(nèi)部被調(diào)用的,而__tmainCRTStartup函數(shù)又是在mainCRTStartup函數(shù)內(nèi)部調(diào)用的。

    為了能更加清楚的看到棧幀創(chuàng)建和銷毀的過程,我們轉(zhuǎn)到上面代碼對應(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);//計算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)過剛才我們的理解,在準備調(diào)用main函數(shù)的時候,調(diào)用main函數(shù)的那個函數(shù)的棧幀已經(jīng)開辟好了。

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    然后將ebp壓入棧幀,保存了指向棧底的ebp的地址,而此時esp指向新的棧頂位置;接著將esp的值賦給了ebp,產(chǎn)生了新的ebp;用esp減去一個16進制數(shù)0E4H(這里就是為main函數(shù)預(yù)開辟空間)。緊接著三個壓棧指令,分別將ebx,esi,edi,壓入棧幀。加載完有效地址以后,將為main函數(shù)預(yù)開辟空間全部初始化為0xCCCCCCCC。最后創(chuàng)建了三個局部變量a,b,ret并進行了初始化。

    Add函數(shù)的調(diào)用

    函數(shù)傳參

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    將b的值存入寄存器eax中,再將eax壓入棧中;將a的值存入寄存器ecx中,再將將ecx壓入棧中;這里看出參數(shù)是從右向左傳遞的。緊接著執(zhí)行call指令,這里就是調(diào)用Add函數(shù),同時將call指令的下一條指令的地址壓入棧中,然后執(zhí)行call指令的時候按F11 , 就進入了Add函數(shù)內(nèi)部。

    Add函數(shù)棧幀的創(chuàng)建

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    首先將main()函數(shù)的ebp壓入棧,保存指向main()函數(shù)棧幀底部的ebp的地址,此時esp指向新的棧頂位置;將esp的值賦給ebp,產(chǎn)生新的ebp,即Add()函數(shù)棧幀的ebp;給esp減去一個16進制數(shù)0E4H,這里是為Add()函數(shù)預(yù)開辟空間;緊接著三個壓棧指令,分別將ebx,esi,edi,壓入棧幀。加載完有效地址以后,將為Add函數(shù)預(yù)開辟空間全部初始化0xCCCCCCCC。在緊接著創(chuàng)建了變量z,將形參的a和b相加的結(jié)果存儲到z中;最后將結(jié)果存儲到eax寄存器中,通過寄存器帶回了函數(shù)的返回值。

    Add函數(shù)棧幀的銷毀

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    edi、esi、ebx依次出棧,esp 會向下移動;然后將ebp的值賦給esp,使esp指向ebp指向的地方;接著ebp 出棧,同時將出棧的內(nèi)容給ebp,此時ebp又指向了main函數(shù)棧幀的底部,最后執(zhí)行ret 指令,表示出棧一次,并跳轉(zhuǎn)到出棧的內(nèi)容的地址處,也就是call指令的下一條指令處。

    main函數(shù)棧幀的銷毀

    C語言函數(shù)棧幀如何創(chuàng)建和銷毀

    main函數(shù)棧幀的銷毀和Add函數(shù)棧幀銷毀的過程的思想都是一樣的,這里就不做多贅述了。

    以上是“C語言函數(shù)棧幀如何創(chuàng)建和銷毀”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI