欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C語(yǔ)言中函數(shù)棧幀的創(chuàng)建和銷毀的深層分析

 更新時(shí)間:2022年04月11日 16:24:44   作者:三分苦  
在C語(yǔ)言中,每一個(gè)正在運(yùn)行的函數(shù)都有一個(gè)棧幀與其對(duì)應(yīng),棧幀中存儲(chǔ)的是該函數(shù)的返回地址和局部變量。從邏輯上講,棧幀就是一個(gè)函數(shù)執(zhí)行的環(huán)境:函數(shù)參數(shù)、函數(shù)的局部變量、函數(shù)執(zhí)行完后返回到哪里等等

一、本文目標(biāo)

1、局部變量是怎么創(chuàng)建的?

2、為什么局部變量的值是隨機(jī)值?

3、函數(shù)是怎么傳參的?傳參的順序是怎樣的?

4、形參和實(shí)參是什么關(guān)系?

5、函數(shù)調(diào)用是怎么做的?

6、函數(shù)調(diào)用結(jié)束后是怎么返回的?

當(dāng)我們深入理解函數(shù)棧幀創(chuàng)建和銷毀,答案自然就清楚了。正文開始:

二、基礎(chǔ)知識(shí)

1、寄存器

寄存器名稱簡(jiǎn)介
eax"累加器"  它是很多加法乘法指令的缺省寄存器。
ebx"基地址"寄存器, 在內(nèi)存尋址時(shí)存放基地址。
ecx計(jì)數(shù)器,是重復(fù)(REP)前綴指令和LOOP指令的內(nèi)定計(jì)數(shù)器。
edx總是被用來(lái)放整數(shù)除法產(chǎn)生的余數(shù)。
esi源索引寄存器
edi目標(biāo)索引寄存器
ebp(棧底指針)"基址指針",存放的是地址,用來(lái)維護(hù)函數(shù)棧幀
esp(棧頂指針)專門用作堆棧指針,存放的是地址,用來(lái)維護(hù)函數(shù)棧幀

2、代碼案例  

本文依賴的編譯器:VS2013

#include<stdio.h>
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

3、總體棧幀概況

每一個(gè)函數(shù)調(diào)用都要在棧區(qū)為它開辟空間,像上述的代碼中,有肉眼可見的main函數(shù)和Add函數(shù),相應(yīng)的需要為它倆開辟空間,但其實(shí)main函數(shù)也是被調(diào)用的,當(dāng)我們針對(duì)上述代碼按下F10,按到return 0時(shí)再按一次,就會(huì)跳出以下界面

 由圖得知,main函數(shù)是被__tmainCRTStartup函數(shù)調(diào)用的,而 __tmainCRTStartup又是被mainCRTStartup調(diào)用的。先看下總體函數(shù)棧幀開辟情況:

兩個(gè)重要知識(shí)點(diǎn):

  • 壓棧(push):給棧頂放一個(gè)元素
  • 出棧(pop)  :從棧頂刪除一個(gè)元素

接下來(lái)會(huì)詳細(xì)講解下函數(shù)棧幀的開辟情況: 

4、所需反匯編代碼總覽

int main()
{
00031410  push        ebp  
00031411  mov         ebp,esp  
00031413  sub         esp,0E4h  
00031419  push        ebx  
0003141A  push        esi  
0003141B  push        edi  
0003141C  lea         edi,[ebp+FFFFFF1Ch]  
00031422  mov         ecx,39h  
00031427  mov         eax,0CCCCCCCCh  
0003142C  rep stos    dword ptr es:[edi]  
    int a = 10;
0003142E  mov         dword ptr [ebp-8],0Ah  
    int b = 20;
00031435  mov         dword ptr [ebp-14h],14h  
    int c = 0;
0003143C  mov         dword ptr [ebp-20h],0
 
    c=Add(a,b);  
00031443  mov         eax,dword ptr [ebp-14h]  
00031446  push        eax  
00031447  mov         ecx,dword ptr [ebp-8]  
0003144A  push        ecx  
0003144B  call        00C210E1  
00031440  add         esp,8  
00031443  mov         dword ptr [ebp-20h],eax  
    printf("%d", c);
00241456  mov         esi,esp  
00241458  mov         eax,dword ptr [ebp-20h]  
0024145B  push        eax  
0024145C  push        245858h  
00241461  call        dword ptr ds:[00249114h]  
00241467  add         esp,8  
0024146A  cmp         esi,esp  
0024146C  call        0024113B  
    return 0;
00241471  xor         eax,eax  
}
00031451  pop         edi  
00031452  pop         esi  
00031453  pop         ebx  
00031454  add         esp,0E4h  
0003145A  cmp         ebp,esp  
0003145C  call        __RTC_CheckEsp (03113Bh)  
00031461  mov         esp,ebp  
00031463  pop         ebp  
00031464  ret  

int Add(int x, int y)
{
000313C0  push        ebp  
000313C1  mov         ebp,esp  
000313C3  sub         esp,0CCh  
000313C9  push        ebx  
000313CA  push        esi  
000313CB  push        edi  
000313CC  lea         edi,[ebp-0CCh]  
000313D2  mov         ecx,33h  
000313D7  mov         eax,0CCCCCCCCh  
000313DC  rep stos    dword ptr es:[edi]  
    int z = 0;
000313DE  mov         dword ptr [ebp-8],0  
    z = x + y;
000313E5  mov         eax,dword ptr [ebp+8]  
000313E8  add         eax,dword ptr [ebp+0ch]  
000313EB  mov         dword ptr [ebp-8],eax  
    return z;
000313EE  mov         eax,dword ptr [ebp-8]  
}
000313F1  pop         edi  
000313F2  pop         esi  
000313F3  pop         ebx  
000313F4  mov         esp,ebp  
000313F6  pop         ebp  
000313F7  ret  

三、函數(shù)棧幀創(chuàng)建銷毀過程

1、_tmainCRTStartup函數(shù)(調(diào)用main函數(shù))棧幀的創(chuàng)建

根據(jù)上文,我們已經(jīng)知曉main函數(shù)是被_tmainCRTStartup函數(shù)所調(diào)用的,自然要為它開辟棧幀,這塊空間應(yīng)該由ebp和sep倆寄存器來(lái)維護(hù),前提是下面高地址,上面低地址。如圖:

 此時(shí)進(jìn)入main函數(shù),首先要push進(jìn)行壓棧:

 push ebp就是把ebp壓到棧頂上,此時(shí)sep相應(yīng)的移動(dòng)到新棧頂上,可以通過監(jiān)視來(lái)驗(yàn)證:

圖示如下:

 接下來(lái)執(zhí)行mov操作:

 此行代碼意思就是把sep賦給ebp,所以ebp指向的位置即為sep所指向的位置,但是源操作地址位置不變,可通過監(jiān)視來(lái)驗(yàn)證

 接著執(zhí)行sub操作:

該操作就是給esp減去個(gè)0E4h ,此時(shí)esp的位置就要往上面去,通過監(jiān)視觀察:

 此時(shí)此刻執(zhí)行完sub操作,其實(shí)就已經(jīng)進(jìn)入到下文的main函數(shù)棧幀的開辟,至此_tmainCRTStartup函數(shù)棧幀的開辟已完成。圖示見下文:

2、main函數(shù)棧幀的創(chuàng)建

接上文,圖示如下:

接下來(lái)進(jìn)行三次push操作: 把ebx、sei、edi順次壓棧壓進(jìn)去,相應(yīng)的esp也要往上走。

通過監(jiān)視看看:

圖示如下:

接下來(lái)執(zhí)行下列三個(gè)步驟

操作lea(load effecitve address)加載有效地址。就是相當(dāng)于把[ebp+FFFFFF1Ch]放到edi里頭,顯示符號(hào)名后[ebp+FFFFFF1Ch]就是[ebp-0E4h],前面已經(jīng)執(zhí)行過-0E4h,這里再執(zhí)行一次放到edi里頭去。接著mov把39h放到ecx里頭去,再mov此時(shí)eax放的就是0CCCCCCCCh 

上述操作執(zhí)行后的目的就是從剛才的edi開始向下的39h次這么多個(gè)dword(1個(gè)word2字節(jié),2dword4個(gè)字節(jié))全部改為0CCCCCCCCh 

通過監(jiān)視看下:

 圖示如下:

至此,main棧幀的開辟已經(jīng)完成,接下來(lái)就要執(zhí)行正式有效代碼,見下文:

3、main函數(shù)內(nèi)執(zhí)行有效代碼(變量)

接下來(lái)執(zhí)行以下操作:

 先mov把0Ah(10)放到ebp-8的位置上,同理把14h(20)放到ebp-14h上,把0放到ebp-20h上,如圖:

 此時(shí)此刻a、b、c這三個(gè)變量均已創(chuàng)建完成,接下來(lái)進(jìn)行Add函數(shù)調(diào)用:先進(jìn)行傳參

首先,mov把ebp-14h(b=20)放到eax里頭。接下來(lái)再push, 壓棧把eax(20)放到棧頂,相應(yīng)esp也要移動(dòng),同理mov把ebp-8(a=10)放到ecx里頭,再push把ecx放到棧頂。如圖所示:

 接著執(zhí)行call操作,調(diào)用Add函數(shù),按F10執(zhí)行到call時(shí),按下F11,此時(shí)就跳到Add函數(shù)內(nèi)部并且把call指令的下一條指令的地址壓到棧頂。這么做的目的是在接下來(lái)跳到Add函數(shù)里去回來(lái)時(shí)方便回到該地址,如圖:

按下F11,此時(shí)就正式進(jìn)入Add函數(shù)內(nèi)部 并為其開辟棧幀,詳情見下文:

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

int Add(int x, int y)
{
000313C0  push        ebp  
000313C1  mov         ebp,esp  
000313C3  sub         esp,0CCh  
000313C9  push        ebx  
000313CA  push        esi  
000313CB  push        edi  
000313CC  lea         edi,[ebp-0CCh]  
000313D2  mov         ecx,33h  
000313D7  mov         eax,0CCCCCCCCh  
000313DC  rep stos    dword ptr es:[edi]  

而前面這些操作跟先前main函數(shù)內(nèi)部操作一樣,其實(shí)就是在為Add函數(shù)準(zhǔn)備我們的棧幀

首先,push ebp把ebp壓棧到棧頂,再mov把esp賦給ebp,再sub,把esp-去0CCh,此步驟就是在為Add函數(shù)開辟空間,接著進(jìn)行三次push,同main函數(shù)那樣,同理,依舊是初始化成CCCCCCCC,詳細(xì)過程不再贅述,跟上文main函數(shù)一樣,如圖所示:

 至此,Add棧幀的開辟已基本完成,接下來(lái)就要執(zhí)行正式有效代碼,見下文:

5、Add函數(shù)內(nèi)執(zhí)行有效代碼

接上文:

    int z = 0;
000313DE  mov         dword ptr [ebp-8],0  
    z = x + y;
000313E5  mov         eax,dword ptr [ebp+8]  
000313E8  add         eax,dword ptr [ebp+0ch]  
000313EB  mov         dword ptr [ebp-8],eax  
    return z;
000313EE  mov         eax,dword ptr [ebp-8]  
}

首先,把0放到ebp-8的位置上,接著mov把ebp+8的值放到eax里頭去,此時(shí)eax就是10。再add給eax加上ebp+0ch,就是把20加進(jìn)去,此時(shí)eax就是30,加完后再把eax(30)放到ebp-8里頭去,最終的結(jié)果(30)放到z里頭去。

此時(shí)Add函數(shù)內(nèi)部有效代碼執(zhí)行完畢,見圖:

接下來(lái)就要進(jìn)行返回了,也就是Add函數(shù)棧幀的銷毀,見下文: 

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

    return z;
000313EE  mov         eax,dword ptr [ebp-8]  
}
000313F1  pop         edi  
000313F2  pop         esi  
000313F3  pop         ebx  
000313F4  mov         esp,ebp  
000313F6  pop         ebp  
000313F7  ret 

上文已經(jīng)知道此時(shí)已經(jīng)把ebp-8的值(30)放到eax里頭去,接下來(lái)執(zhí)行三次pop,一次彈出,esp就會(huì)加加一次,如圖:

 接著,把ebp賦給esp,再pop把ebp彈出,此時(shí)esp也要移動(dòng),此時(shí)esp和ebp又回到了先前維護(hù)main函數(shù)棧幀的樣子。如圖所示:

 此時(shí)esp指向的就是call指令的下一條指令的地址,再按一次F10,此時(shí)反匯編就會(huì)這樣:

0003144B  call        00C210E1  
00031440  add         esp,8  
00031443  mov         dword ptr [ebp-20h],eax  
    printf("%d", c);
00241456  mov         esi,esp  
00241458  mov         eax,dword ptr [ebp-20h]  
0024145B  push        eax  
0024145C  push        245858h  
00241461  call        dword ptr ds:[00249114h]  
00241467  add         esp,8  
0024146A  cmp         esi,esp  
0024146C  call        0024113B  
    return 0;
00241471  xor         eax,eax  
}

此時(shí)我們就會(huì)明白先前存放call指令的下一條指令的地址就是為了方便回來(lái),先前ret執(zhí)行后esp的位置發(fā)生變化:

此時(shí)Add函數(shù)的棧幀算是真正銷毀,接下來(lái)進(jìn)行main函數(shù)棧幀的銷毀 。

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

0003144B  call        00C210E1  
00031440  add         esp,8  
00031443  mov         dword ptr [ebp-20h],eax  
    printf("%d", c);
00241456  mov         esi,esp  
00241458  mov         eax,dword ptr [ebp-20h]  
0024145B  push        eax  
0024145C  push        245858h  
00241461  call        dword ptr ds:[00249114h]  
00241467  add         esp,8  
0024146A  cmp         esi,esp  
0024146C  call        0024113B  
    return 0;
00241471  xor         eax,eax  
}

通過反匯編代碼得知,此時(shí)指向add操作把esp加上8,此時(shí)就把x和y這兩個(gè)形參釋放回來(lái)了,指向如圖所示位置:

接下來(lái)mov把eax放到ebp-20h上,而eax就是我們出Add函數(shù)時(shí)計(jì)算的和,此時(shí)和就被我們帶回來(lái)了,接下來(lái)就是main函數(shù)棧幀的銷毀了,跟上文Add函數(shù)棧幀的銷毀沒有太大區(qū)別,這里不多做贅述。

而反匯編代碼如下:

00241471  xor         eax,eax  
}
00031451  pop         edi  
00031452  pop         esi  
00031453  pop         ebx  
00031454  add         esp,0E4h  
0003145A  cmp         ebp,esp  
0003145C  call        __RTC_CheckEsp (03113Bh)  
00031461  mov         esp,ebp  
00031463  pop         ebp  
00031464  ret  

四、總結(jié)

至此,函數(shù)棧幀的創(chuàng)建和銷毀正式結(jié)束,而本文一開始的幾個(gè)問題(目標(biāo))也能清晰得知:

如下:

1、局部變量是怎么創(chuàng)建的?

首先,為函數(shù)分配好棧幀空間并初始化后,然后給局部變量在棧幀里頭分配一點(diǎn)空間。

2、為什么局部變量的值是隨機(jī)值?

因?yàn)殡S機(jī)值是我們?cè)陂_辟棧幀時(shí)就放進(jìn)去的,而我們初始化的時(shí)候,就是把隨機(jī)值給覆蓋了。

3、函數(shù)是怎么傳參的?傳參的順序是怎樣的?

當(dāng)我要調(diào)用函數(shù)之前,就已經(jīng)push、push把這兩個(gè)參數(shù)從右向左壓棧壓進(jìn)去,當(dāng)我們真正進(jìn)入形參函數(shù)的時(shí)候,在Add函數(shù)棧幀里頭通過指針的偏移量找到了形參。

4、形參和實(shí)參是什么關(guān)系?

形參確實(shí)是在壓棧時(shí)開辟的空間,形參和實(shí)參只是值上是相同的,空間上是獨(dú)立的,形參是實(shí)參的一份臨時(shí)拷貝,改變形參不會(huì)影響實(shí)參。

5、函數(shù)調(diào)用結(jié)束后是怎么返回的?

我們?cè)谡{(diào)用之前就已經(jīng)把call指令下一條指令的地址給壓進(jìn)去,當(dāng)函數(shù)調(diào)用完要返回的時(shí)候,就會(huì)跳轉(zhuǎn)到call指令下一條指令的地址,返回值是通過寄存器帶回來(lái)的。

到此這篇關(guān)于C語(yǔ)言中函數(shù)棧幀的創(chuàng)建和銷毀的深層分析的文章就介紹到這了,更多相關(guān)C語(yǔ)言 函數(shù)棧幀內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論