国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

【C語言知識精講②】函數棧幀的創建和銷毀(全程圖解)

davidac / 3071人閱讀

摘要:這里分塊講解六函數棧幀的銷毀過程一解析的作用是將棧頂的數據彈出,彈出數據儲存到相應寄存器中。

?前言?

讀完這篇博客,你可以明白什么?

①局部變量到底是怎么在棧上創建的?

②為什么局部變量不初始化為隨機值?

③函數是怎么傳參的?傳參的先后順序是什么?

④形參和實參是什么關系?

⑤函數調用是怎么實現的?

⑥函數調用后是怎么返回的?? ?

?在這篇博客里,我將帶領大家利用反匯編從底層上理解,不用擔心,都是零基礎入門的。當你學完這篇博客去面試,面試官會非常高興,覺得這小伙子真??。所以學起來吧!

???作者概況:? 就讀南京郵電大學努力學習的大一小伙

???聯系方式:2879377052(QQ小號)? ? ? ? ? ? ?

???資源推薦:C語言從入門到進階

???今日書籍分享:??《深入理解計算機系統》? ? ? ? ? ? ? ? ???

目錄

一、寄存器

二、main函數的調用

三、準備階段

?四、main棧幀的創建分析

?五、add函數棧幀的創建

?六、add函數棧幀的銷毀?

七、main函數棧幀的銷毀

八、完整反匯編代碼

九、后記


一、寄存器

在C語言中我們可以把寄存器當成指針來看待,他可以指向一塊空間,也可以用來存儲數據。現在向大家介紹以下幾種基本寄存器

參考博客:函數棧幀的創建和銷毀(圖解)

寄存器名稱功能
①eax"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
②ebx"基地址"(base)寄存器, 在內存尋址時存放基地址
③ecx計數器(counter),計數寄存器,用于循環操作,比如重復的字符存儲操作,或者數字統計。
④edx作為EAX的溢出寄存器,總是被用來放整數除法產生的余數。
⑤esi源變址寄存器,主要用于存放存儲單元在段內的偏移量。通常在內存操作指令中作為“源地址指針”使用。
⑥edi目的變址寄存器,主要用于存放存儲單元在段內的偏移量。
⑦esp?棧指針寄存器(extended stack pointer),其內存放著一個指針,該指針永遠指向棧最上面一個棧幀的棧頂。esp用于堆棧操作,被形象地稱為棧頂指針。堆棧的頂部是地址小的區域,壓入堆棧的數據越多,esp也就越來越小。在32位平臺上,esp每次減少4字節。
⑧ebp?基址指針寄存器(extended base pointer),其內存放著一個基址指針,該指針永遠指向系統棧最上面一個棧幀的底部。基址指針,又被形參稱作棧底指針,一般與esp配合使用,可以存取某時刻的esp,這個時刻就是進入一個函數內后,CPU會將esp的值賦給ebp,此時就可以通過ebp對棧進行操作。

二、main函數的調用

main函數其實也是被其他函數調用的,函數調用關系如下:

mainCRTStartup? →? ?__tmainCRTStartup? →? main?

?

(在調用堆棧中即可觀察到)?

三、準備階段

①首先我們寫一段簡單的代碼,將代碼的每個過程拆分的盡可能簡單以便于我們對過程的觀察

int add(int x,int y){	int z = 0;	z = x + y;	return z;}int main(){	int a = 10;	int b = 20;	int c = add(a,b);	return 0;}

?②在調試狀態下轉入反匯編模式

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)?

?看不懂沒關系,現在逐一解釋

我們知道,函數的調用都需要在棧區上開辟空間,那么我們先來解答幾個問題:

1.什么是棧?

【答】棧是一種數據結構,它按照后進先出的原則存儲數據,先進入的數據被壓入棧底,最后的數據在棧頂,需要讀數據的時候從棧頂開始彈出數據。棧區內存空間的使用是從高地址向低地址處使用的。

2.什么是壓棧?什么是出棧?

【答】一個形象的比喻就是機槍彈夾。壓棧的過程就是壓入一個元素,相當于向機槍彈夾壓入子彈;出棧的過程就是彈出一個元素,相當于子彈彈出來的過程。這正好對應了棧的結構特點——先進入的數據被壓在棧底,后進入的數據在棧頂。

3.為什么一個數據在內存中是“倒著”存放的(注意顯示的是16進制)?

【答】數據的存儲涉及大小端問題:

①什么是大小端

>大端(存儲)模式是指數據的低位保存在內存的高地址中,而數據的高位,保存在內存的低地址中。
>小端(存儲)模式是指數據的低位保存在內存的低地址中,而數據的高位,,保存在內存的高地址中。

?

?②為什么有大小端

存放的內容大于1個字節,必然存在著如何將多字節安排的問題。因此就出現了大小端。具體大小端取決于編譯器,一般為小端存儲。?

?四、main棧幀的創建分析

1.我們之前提到,main函數是由__tmianCRTStartup函數調用的,所以在創建main函數棧幀前,ebp和esp寄存器維護--tmainCRTStartup的棧區,分別存放指向棧幀的棧頂和棧底。

2.同時注意,棧區上內存的使用是從高地址向低地址處使用的

?

【過程一:??push? ?ebp?

?【解釋】push指令的作用:它首先減小esp的值,再將源操作數復制到棧地址,每次esp地址減去四字節。最終效果就是在棧頂壓入一個元素,元素的值為ebp的地址。(4個字節)

【補充】內容不隨指令執行而變化的操作數為源操作數,內容隨執行指令而改變的操作數為目標操作數。

?

?

?

?【過程二:? mov? ? ebp,esp?】

?【解釋】mov指令作用:將一個數據從源地址傳送到目標地址,源操作地址的內容不變。最終效果是將esp的地址賦值給ebp。寄存器指向的空間發生改變。

?

?

?【過程三:? ?sub? ? esp,0E4h】

?【解釋】sub指令的作用:減操作指令,地址減去相應的數值。最終效果就是esp的地址減去0E4h。

?

?

?【過程四:push ebx,esi,edi】

?【解釋】前面我們提到過push的過程就壓入元素的過程。那壓入這三個元素有什么用呢?等會就明白。

?

??【過程五:lea? edi,[ebp- 0E4hh]? /? mov ? ecx,9? /? mov ? eax, 0CCCCCCCCh】

【解釋】Load Effective Address,即裝入有效地址的意思,它的操作數就是地址。在這里的效果就是將ebp+FFFFFF1Ch的值賦給edi。勾選“顯示符號名后可以發現:

?

?ebp - 0E4h不正是當初esp - 0E4h時的地址嗎?

?

?執行mov指令后,我們可以發現寄存器eax,ecx的值發生變化。

?

?【過程六:?rep stos ? ?dword ptr es:[edi]?】???

?【解釋】rep指令的作用是:重復后面的指令。stos指令的作用是將eax中的值拷貝到es:edi指向的地址。ecx表示重復操作的次數。dword表示4個字節。所以整句指令的作用是:從edi開始,向高地址方向,將ecx個4字節內存全部修改為eax的值。

?

?(可以看到,執行操作后edi上相應數量的四字節內存被賦值為cc cc cc cc。這也解釋了為什么我們不初始化,變量默認的初始值為cc cc cc cc)

?

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)??

【過程七:mov? ? ?dword? ptr? [ebp - 14h] ,14h】?

【解析】將ebp - 14地址處的四字節內容修改為14h(10進制中的20),也就是完成了給b賦值為20的動作。下一條語句同理。

?

?

??至此main函數的棧幀創建的準備階段完成。我們準備進入add函數

【過程八:mov? ?eax,dword ptr [ebp -?8h]? ?/? ?push ? ?eax】

【解析】將ebp - 14地址處的數值儲存到eax處;壓棧,將一個數值與eax相等的元素壓入棧中。ebp - 14這個地址好像似曾相識,沒錯,這正是b的地址,所以這條語句的作用實際上即使將b的值傳到eax中。同理,a的值被存儲到ecx中去。

(實際上,上述過程解釋了函數究竟是如何傳參的。傳參的順序和變量創建的順序恰好相反,先傳b再傳a。同時注意函數傳參并不是在add函數棧幀內完成的,而是在main函數的棧幀內完成,通過寄存器eax和ecx實現變量的傳遞)

?

?【過程九:call? ? 00AD1023】?

?【解析】call指令的作用是:將下一條的指令的ip壓入棧中,并轉移到即將被調用的子程序。相當于push ip +? jmp near ptr 標號。我們現在來觀察棧頂的變化。

??在call指令一行們按F11觀察call的作用

?

我們驚奇的發現棧頂自動壓入了一個元素,元素的值為——call指令的下一條指令的地址?。那壓入這個元素有什么用呢?試想,當call指令調用add函數后,我們跳轉到add函數內部,那函數結束后如何保證我們從add函數后面的語句繼續執行呢?靠的就是棧頂壓入的地址,根據這個地址我們可以回到call指令的下一條語句。

?

再次按下F11后我們就跳轉到add函數內部。


?五、add函數棧幀的創建

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)??

?這一部分的操作和main函數內完全一致,都是為棧幀的創建做準備。畫一個動圖,不再贅述。

?

?我們現在研究接下來指令。?

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)??

?【過程一:mov? ? dword ptr [ebp-8],0】

?【解析】將ebp - 8 的地址賦值為0。也就代表著將Z初始化為0

?

?

【過程二:mov? ? eax, dword ptr [ebp+8]】

?【解析】將 ebp + 8 (從上圖觀察)處的值賦值給eax。此時eax儲存著形參a的值

【過程三:add ? ? eax, dword ptr [ebp+0Ch]】

【解析】將eax加上 ebp + 12 處的值。此時eax表示這a+b的值

【過程四:mov? ?? dword ptr [ebp-8], eax】

【解析】將eax儲存的a+b的值傳送到ebp - 8的地址處,也就是賦值給變量Z

【總結】從上面我們也可以加深這樣的認識:形參只是實參的一個臨時拷貝,所以修改形參當然不會影響實參。?

?

?

【過程五:mov? ? ?eax, dword ptr [ebp-8]】

【解析】我們知道函數內創建的臨時變量出函數后被銷毀,那返回值是如何被帶回主函數的呢?靠的就是寄存器eax。這一步的操作就是 return返回 值的過程。

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)??


?六、add函數棧幀的銷毀?

【過程一:pop ? ?edi / esi / ebx】

【解析】pop的作用是將棧頂的數據彈出,彈出數據儲存到相應寄存器中。每次pop過程中esp的地址自動加4字節。

?

【過程二:mov? ? esp, ebp?】

?【解析】一句話就回收了為add函數開辟的內存

?

【過程三:pop ebp】

【解析】彈出棧頂的元素,并將彈出的數據儲存到ebp寄存器中。由于此時的棧頂元素事先存入main函數中ebp的地址,所以pop ebp時,將main函數中的ebp元素的地址存入ebp寄存器中

?

?【過程四:ret】

?【解析】ret指令的作用實際相當于 pop IP。在這里實際就是彈出了棧頂事先存儲的add下一條指令的地址,并跳轉到該地址處。

?

?

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)??

?【過程五:mov ? ? dword ptr [ebp-20h],eax】

?

?【解析】監視我們可以發現ebp - 20所指向的對象就是c,所以這條語句的作用就是將返回值存儲到c中

?


七、main函數棧幀的銷毀

?

(完整連續反匯編代碼請看文章結尾。這里分塊講解)??

?如何理解之后的語句呢,其實和add函數棧幀的銷毀基本是一致的,因為我們前面提到,main函數也是被其他函數調用的。以此類推。


八、完整反匯編代碼

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-0E4h]  00031422  mov         ecx,39h  00031427  mov         eax,0CCCCCCCCh  0003142C  rep stos    dword ptr es:[edi]  	int a = 10;0003142E  mov         dword ptr [a],0Ah  	int b = 20;00031435  mov         dword ptr [b],14h  	int c = add(a,b);0003143C  mov         eax,dword ptr [b]  0003143F  push        eax  00031440  mov         ecx,dword ptr [a]  00031443  push        ecx  00031444  call        _add (0310E6h)  00031449  add         esp,8  0003144C  mov         dword ptr [c],eax  	return 0;0003144F  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 [z],0  	z = x + y;000313E5  mov         eax,dword ptr [x]  000313E8  add         eax,dword ptr [y]  000313EB  mov         dword ptr [z],eax  	return z;000313EE  mov         eax,dword ptr [z]  }000313F1  pop         edi  000313F2  pop         esi  000313F3  pop         ebx  000313F4  mov         esp,ebp  000313F6  pop         ebp  000313F7  ret  

?【注】這是后面補充的便于大家對知識的掌握有一個連貫性,所以在地址上有些不同。關注重要的指令即可。


九、后記

? ? ? ? 恭喜你學完了函數棧幀的創建和銷毀!覺得不錯可以點個贊。大家最近是不是有一大波考試呢?祝大家考的都會,蒙的全對。

?

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/125080.html

相關文章

  • C語言深層理解:函數幀的創建銷毀

    摘要:語言深層理解函數中棧幀的創建與銷毀引言引言問題一引言問題二引言問題三一棧的簡單認識內存的簡單了解棧的簡單了解棧的定義棧的結構二寄存器與簡單的匯編指令寄存器的定義寄存器的分類簡單的匯編指令三棧幀的創建于銷毀調試調用堆棧調 ...

    archieyang 評論0 收藏0
  • C語言學習筆記—P17(函數幀的創建銷毀<超詳解版>+圖解+題例)

    摘要:目錄前言由于作者水平有限,文章難免存在謬誤之處,敬請讀者斧正,俚語成篇,懇望指教前言由于作者水平有限,文章難免存在謬誤之處,敬請讀者斧正,俚語成篇,懇望指教作者新曉故知作者新曉故知那些代碼背后的故事那些代碼背后的故事通過 目錄 前言:●由于作者水平有限,文章難免存在謬誤之處,敬請讀者斧正,俚...

    gnehc 評論0 收藏0
  • 函數棧幀解析

    摘要:函數棧幀的銷毀匯編語言了解函數傳參函數返回值如何返回函數中變量如何初始化和賦值函數執行結束后系統進行了什么操作 文章目錄 一、什么是函數棧幀 1.寄存器2.函數棧幀3.棧幀的作用和維護4.棧幀結構二、函數棧幀的創建? 1.匯編2.main函數3.Add函數的創建三、函數...

    MonoLog 評論0 收藏0
  • 理解函數棧幀

    摘要:同時,和所指示的位置會隨著函數棧幀的創建和銷毀而不斷的發生改變。再次執行函數棧幀的創建操作。函數的返回值會存在一個寄存器中當函數棧幀釋放后,返回值不會隨之消失。二函數棧幀的銷毀將一些函數調用中使用的寄存器彈出棧。 ...

    番茄西紅柿 評論0 收藏0
  • C語言】從入門到入土(指針篇)

    摘要:在位機器上,如果有個地址線,那一個指針變量的大小是個字節,才能存放一個地址。就是一個指針變量,也有自己的類型,指針變量的類型我們可以發現指針的定義方式是類型星號。也就是說存儲什么變量類型就用什么指針變量類型。 ...

    陳偉 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<