<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
1、區域性變數是怎麼建立的?
2、為什麼區域性變數的值是隨機值?
3、函數是怎麼傳參的?傳參的順序是怎樣的?
4、形參和實參是什麼關係?
5、函數呼叫是怎麼做的?
6、函數呼叫結束後是怎麼返回的?
當我們深入理解函數棧幀建立和銷燬,答案自然就清楚了。正文開始:
暫存器名稱 | 簡介 |
eax | "累加器" 它是很多加法乘法指令的預設暫存器。 |
ebx | "基地址"暫存器, 在記憶體定址時存放基地址。 |
ecx | 計數器,是重複(REP)字首指令和LOOP指令的內定計數器。 |
edx | 總是被用來放整數除法產生的餘數。 |
esi | 源索引暫存器 |
edi | 目標索引暫存器 |
ebp | (棧底指標)"基址指標",存放的是地址,用來維護函數棧幀 |
esp | (棧頂指標)專門用作堆疊指標,存放的是地址,用來維護函數棧幀 |
本文依賴的編譯器: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("%dn", c); return 0; }
每一個函數呼叫都要在棧區為它開闢空間,像上述的程式碼中,有肉眼可見的main函數和Add函數,相應的需要為它倆開闢空間,但其實main函數也是被呼叫的,當我們針對上述程式碼按下F10,按到return 0時再按一次,就會跳出以下介面
由圖得知,main函數是被__tmainCRTStartup函數呼叫的,而 __tmainCRTStartup又是被mainCRTStartup呼叫的。先看下總體函數棧幀開闢情況:
兩個重要知識點:
接下來會詳細講解下函數棧幀的開闢情況:
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
根據上文,我們已經知曉main函數是被_tmainCRTStartup函數所呼叫的,自然要為它開闢棧幀,這塊空間應該由ebp和sep倆暫存器來維護,前提是下面高地址,上面低地址。如圖:
此時進入main函數,首先要push進行壓棧:
push ebp就是把ebp壓到棧頂上,此時sep相應的移動到新棧頂上,可以通過監視來驗證:
圖示如下:
接下來執行mov操作:
此行程式碼意思就是把sep賦給ebp,所以ebp指向的位置即為sep所指向的位置,但是源操作地址位置不變,可通過監視來驗證
接著執行sub操作:
該操作就是給esp減去個0E4h ,此時esp的位置就要往上面去,通過監視觀察:
此時此刻執行完sub操作,其實就已經進入到下文的main函數棧幀的開闢,至此_tmainCRTStartup函數棧幀的開闢已完成。圖示見下文:
接上文,圖示如下:
接下來進行三次push操作: 把ebx、sei、edi順次壓棧壓進去,相應的esp也要往上走。
通過監視看看:
圖示如下:
接下來執行下列三個步驟
操作lea(load effecitve address)載入有效地址。就是相當於把[ebp+FFFFFF1Ch]放到edi裡頭,顯示符號名後[ebp+FFFFFF1Ch]就是[ebp-0E4h],前面已經執行過-0E4h,這裡再執行一次放到edi裡頭去。接著mov把39h放到ecx裡頭去,再mov此時eax放的就是0CCCCCCCCh
上述操作執行後的目的就是從剛才的edi開始向下的39h次這麼多個dword(1個word2位元組,2dword4個位元組)全部改為0CCCCCCCCh
通過監視看下:
圖示如下:
至此,main棧幀的開闢已經完成,接下來就要執行正式有效程式碼,見下文:
接下來執行以下操作:
先mov把0Ah(10)放到ebp-8的位置上,同理把14h(20)放到ebp-14h上,把0放到ebp-20h上,如圖:
此時此刻a、b、c這三個變數均已建立完成,接下來進行Add函數呼叫:先進行傳參
首先,mov把ebp-14h(b=20)放到eax裡頭。接下來再push, 壓棧把eax(20)放到棧頂,相應esp也要移動,同理mov把ebp-8(a=10)放到ecx裡頭,再push把ecx放到棧頂。如圖所示:
接著執行call操作,呼叫Add函數,按F10執行到call時,按下F11,此時就跳到Add函數內部並且把call指令的下一條指令的地址壓到棧頂。這麼做的目的是在接下來跳到Add函數裡去回來時方便回到該地址,如圖:
按下F11,此時就正式進入Add函數內部 併為其開闢棧幀,詳情見下文:
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函數內部操作一樣,其實就是在為Add函數準備我們的棧幀
首先,push ebp把ebp壓棧到棧頂,再mov把esp賦給ebp,再sub,把esp-去0CCh,此步驟就是在為Add函數開闢空間,接著進行三次push,同main函數那樣,同理,依舊是初始化成CCCCCCCC,詳細過程不再贅述,跟上文main函數一樣,如圖所示:
至此,Add棧幀的開闢已基本完成,接下來就要執行正式有效程式碼,見下文:
接上文:
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裡頭去,此時eax就是10。再add給eax加上ebp+0ch,就是把20加進去,此時eax就是30,加完後再把eax(30)放到ebp-8裡頭去,最終的結果(30)放到z裡頭去。
此時Add函數內部有效程式碼執行完畢,見圖:
接下來就要進行返回了,也就是Add函數棧幀的銷燬,見下文:
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
上文已經知道此時已經把ebp-8的值(30)放到eax裡頭去,接下來執行三次pop,一次彈出,esp就會加加一次,如圖:
接著,把ebp賦給esp,再pop把ebp彈出,此時esp也要移動,此時esp和ebp又回到了先前維護main函數棧幀的樣子。如圖所示:
此時esp指向的就是call指令的下一條指令的地址,再按一次F10,此時反組合就會這樣:
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
}
此時我們就會明白先前存放call指令的下一條指令的地址就是為了方便回來,先前ret執行後esp的位置發生變化:
此時Add函數的棧幀算是真正銷燬,接下來進行main函數棧幀的銷燬 。
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
}
通過反組合程式碼得知,此時指向add操作把esp加上8,此時就把x和y這兩個形參釋放回來了,指向如圖所示位置:
接下來mov把eax放到ebp-20h上,而eax就是我們出Add函數時計算的和,此時和就被我們帶回來了,接下來就是main函數棧幀的銷燬了,跟上文Add函數棧幀的銷燬沒有太大區別,這裡不多做贅述。
而反組合程式碼如下:
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
至此,函數棧幀的建立和銷燬正式結束,而本文一開始的幾個問題(目標)也能清晰得知:
如下:
1、區域性變數是怎麼建立的?
首先,為函數分配好棧幀空間並初始化後,然後給區域性變數在棧幀裡頭分配一點空間。
2、為什麼區域性變數的值是隨機值?
因為隨機值是我們在開闢棧幀時就放進去的,而我們初始化的時候,就是把隨機值給覆蓋了。
3、函數是怎麼傳參的?傳參的順序是怎樣的?
當我要呼叫函數之前,就已經push、push把這兩個引數從右向左壓棧壓進去,當我們真正進入形參函數的時候,在Add函數棧幀裡頭通過指標的偏移量找到了形參。
4、形參和實參是什麼關係?
形參確實是在壓棧時開闢的空間,形參和實參只是值上是相同的,空間上是獨立的,形參是實參的一份臨時拷貝,改變形參不會影響實參。
5、函數呼叫結束後是怎麼返回的?
我們在呼叫之前就已經把call指令下一條指令的地址給壓進去,當函數呼叫完要返回的時候,就會跳轉到call指令下一條指令的地址,返回值是通過暫存器帶回來的。
到此這篇關於C語言中函數棧幀的建立和銷燬的深層分析的文章就介紹到這了,更多相關C語言 函數棧幀內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45