摘要:語言深層理解函數(shù)中棧幀的創(chuàng)建與銷毀引言引言問題一引言問題二引言問題三一棧的簡單認(rèn)識(shí)內(nèi)存的簡單了解棧的簡單了解棧的定義棧的結(jié)構(gòu)二寄存器與簡單的匯編指令寄存器的定義寄存器的分類簡單的匯編指令三棧幀的創(chuàng)建于銷毀調(diào)試調(diào)用堆棧調(diào)
我們?cè)趯W(xué)習(xí)C語言的過程中,一定會(huì)經(jīng)歷過或者思考過下面的問題:
①當(dāng)我們C語言中進(jìn)行printf操作時(shí),有時(shí)會(huì)出現(xiàn)"燙燙燙"的字眼,那么為什么會(huì)出現(xiàn)"燙燙燙"這樣的字眼呢?
②我們?cè)趯W(xué)習(xí)與使用函數(shù)時(shí),當(dāng)我們進(jìn)行函數(shù)的值傳遞時(shí),我們被告知當(dāng)被調(diào)函數(shù)中,形參的改變,并不會(huì)改變傳參變量(實(shí)參)的數(shù)據(jù)內(nèi)容,那么為什么不會(huì)改變傳遞的參數(shù)的內(nèi)容呢?
③我們?cè)诘诙€(gè)問題中,還會(huì)被告知,當(dāng)進(jìn)行參數(shù)值傳遞時(shí),在被調(diào)函數(shù)中,其實(shí)那些參數(shù)的值是實(shí)參的一份令時(shí)拷貝的數(shù)據(jù),那么為什么是臨時(shí)拷貝的數(shù)據(jù)呢?
下面我們就來徹底理解這些情況的真實(shí)原因與過程。
我們?cè)诔跗趯W(xué)習(xí)C語言時(shí),會(huì)學(xué)到各種變量,有的是可變變量,有的是不可修改的常量,又會(huì)接觸到一些棧,堆的概念,下面的圖例,是為了我們便于我們了解函數(shù)棧幀的創(chuàng)建與銷毀的簡單內(nèi)存圖解:
定義:棧是一種特殊的線性表,其只允許在固定的一端進(jìn)行插入和刪除元素操作。
①進(jìn)行數(shù)據(jù)插入和刪除操作的一端稱為棧頂,另一端稱為棧底。棧中的數(shù)據(jù)元素遵守后進(jìn)先出LIFO(Last In First Out)的原則
②壓棧:棧的插入操作叫做進(jìn)棧/壓棧/入棧,入數(shù)據(jù)在棧頂。
③出棧:棧的刪除操作叫做出棧。出數(shù)據(jù)也在棧頂。
①定義:寄存器是中央處理器內(nèi)的組成部分。寄存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、數(shù)據(jù)和地址。在中央處理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序計(jì)數(shù)器(PC)。在中央處理器的算術(shù)及邏輯部件中,存器有累加器(ACC)。
寄存器的基本單元是 D觸發(fā)器,
按照其用途分為基本寄存器和移位寄存器
基本寄存器是由 D觸發(fā)器組成,在 CP 脈沖作用下,每個(gè) D觸發(fā)器能夠寄存一位二進(jìn)制碼。在 D=0 時(shí),寄存器儲(chǔ)存為 0,在 D=1 時(shí),寄存器儲(chǔ)存為 1。在低電平為 0、高電平為 1 時(shí),需將信號(hào)源與 D 間連接一反相器,這樣就可以完成對(duì)數(shù)據(jù)的儲(chǔ)存。
需要強(qiáng)調(diào)的是,目前大型數(shù)字系統(tǒng)都是基于時(shí)鐘運(yùn)作的,其中寄存器一般是在時(shí)鐘的邊緣被觸發(fā)的,基于電平觸發(fā)的已較少使用。(通常說的CPU的頻率就是指數(shù)字集成電路的時(shí)鐘頻率)
移位寄存器按照移位方向可以分為單向移位寄存器和雙向移位寄存器。單向移位寄存器是由多個(gè) D 觸發(fā)器串接而成,在串口 Di 輸入需要儲(chǔ)存的數(shù)據(jù),觸發(fā)器 FF0 就能夠儲(chǔ)存當(dāng)前需要儲(chǔ)存數(shù)據(jù),在 CP 發(fā)出一次時(shí)鐘控制脈沖時(shí),串口 Di 同時(shí)輸入第二個(gè)需要儲(chǔ)存是的數(shù)據(jù),而第一個(gè)數(shù)據(jù)則儲(chǔ)存到觸發(fā)器 FF1 中。雙向移位寄存器按圖中方式排列,調(diào)換連接端順序,可以控制寄存器向左移位,增加控制電路可以使寄存器右移,這樣構(gòu)成雙向移位寄存器。
②特點(diǎn):
寄存器又分為內(nèi)部寄存器與外部寄存器,所謂內(nèi)部寄存器,其實(shí)也是一些小的存儲(chǔ)單元,也能存儲(chǔ)數(shù)據(jù)。但同存儲(chǔ)器相比,寄存器又有自己獨(dú)有的特點(diǎn):
a、寄存器位于CPU內(nèi)部,數(shù)量很少,僅十四個(gè)
b、寄存器所能存儲(chǔ)的數(shù)據(jù)不一定是8bit,有一些寄存器可以存儲(chǔ)16bit數(shù)據(jù),對(duì)于386/486處理器中的一些寄存器則能存儲(chǔ)32bit數(shù)據(jù)
c、每個(gè)內(nèi)部寄存器都有一個(gè)名字,而沒有類似存儲(chǔ)器的地址編號(hào)。
③用途:
1.可將寄存器內(nèi)的數(shù)據(jù)執(zhí)行算術(shù)及邏輯運(yùn)算
2.存于寄存器內(nèi)的地址可用來指向內(nèi)存的某個(gè)位置,即尋址
3.可以用來讀寫數(shù)據(jù)到電腦的周邊設(shè)備。
寄存器 | 用途 |
---|---|
eax | 累加寄存器,相對(duì)于其他寄存器,在運(yùn)算方面比較常用 |
ebx | 基地址寄存器,作為內(nèi)存偏移指針使用 |
edi | 在內(nèi)存操作指令中作為“目的地址”使用 |
esi | 在內(nèi)存操作指令中作為“源地址指針”使用 |
ecx | 計(jì)數(shù)器,用于特定的技術(shù) |
edx | 作為EAX的溢出寄存器,(除法產(chǎn)生的余數(shù)) |
esp | 指針的寄存器,用于堆棧操作。被形象地稱為棧頂指針,堆棧的頂部是地址小的區(qū)域,壓入堆棧的數(shù)據(jù)越多,ESP也就越來越小。在32位平臺(tái)上,ESP每次減少4字節(jié)。 |
ebp | 基址指針,指棧的棧底指針 |
匯編指令 | 對(duì)應(yīng)操作 |
---|---|
push | 壓棧 |
pop | 出棧 |
move | move A,B 將A移動(dòng)到當(dāng)前B的位置 |
call | 將程序的執(zhí)行交給其他代碼段(即函數(shù)的調(diào)用) |
lea | 加載有效地址 |
ret | 子程序返回指令 |
sub | 減法操作 |
add | 加法操作 |
經(jīng)過上面關(guān)于棧與寄存器的簡單解釋,我們開始本文章的重點(diǎn),對(duì)函數(shù)中的棧幀的創(chuàng)建與銷毀的理解
為了方便我們理解,我們用下面的代碼進(jìn)行講解:
#include int Add(int x, int y){ int z = 0; z = x + y; return z;}int main(){ int a = 20; int b = 10; int c = 0; c = Add(a, b); printf("%d/n", c); return 0;}
這里我們使用后的VS2013編譯器進(jìn)行講解,當(dāng)我們使用不同的編譯器去執(zhí)行代碼與進(jìn)行理解時(shí),會(huì)有一些偏差
①我們首先按F10進(jìn)行調(diào)試,進(jìn)入調(diào)試之后,我們按照下面的操作,調(diào)用堆棧窗口:
②當(dāng)我們進(jìn)入堆棧窗口之后,我們?cè)诔绦蛑衋nF10進(jìn)行程序,當(dāng)程序進(jìn)行結(jié)束之后,我們的堆棧窗口出現(xiàn)下圖:上圖的內(nèi)容是告訴我們:
main函數(shù)在VS2013中任然是被調(diào)函數(shù),main函數(shù)首先被_tmainCRTSartup()函數(shù)調(diào)用,而_tmainCRTSartup()函數(shù)被mainCRTSartup函數(shù)調(diào)用;
③根據(jù)上述內(nèi)容,我們可以得出關(guān)于當(dāng)前程序的棧幀簡圖:
①現(xiàn)在我們?yōu)榱松钊肓私馕覀兊臈侨绾蝿?chuàng)建與銷毀的過程,我們按照下圖步驟進(jìn)行操作:
此時(shí)我們獲取了程序的反匯編語言,通過反匯編語言,我們才能夠深入了解程序的進(jìn)行過程與棧幀的創(chuàng)建與銷毀過程,也正是通過對(duì)反匯編的分析,我們才能夠解決我們引言中的問題;
①這里我們?cè)谶M(jìn)行對(duì)main函數(shù)的反匯編語言進(jìn)行分析前,我們先對(duì)補(bǔ)充一些內(nèi)容的講解:
a、在函數(shù)的棧幀中,ebp和esp這兩個(gè)寄存器是存放地址的,也是這兩個(gè)地址用來維護(hù)函數(shù)的棧幀;
b、在esp和ebp這兩個(gè)寄存器中,esp寄存器存放的是函數(shù)的棧頂?shù)刂罚簿褪菞m斨羔?;ebp寄存器存放的是函數(shù)的棧底地址,也就是棧底指針;
舉例(如圖):
②我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
壓棧操作:將ebp移動(dòng)到當(dāng)前棧的棧底的位置,然后esp指針會(huì)自動(dòng)上移,指向壓棧進(jìn)入棧的ebp
也就是將t_mainSRTSartup函數(shù)的ebp的值存放到棧頂,占用一個(gè)內(nèi)存空間;
舉例(如圖):
③我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
將ebp指針指向當(dāng)前esp指針指向的位置
通過監(jiān)視窗口,從地址進(jìn)行觀察,可以確定,ebp指向了esp指向的位置,此時(shí)兩個(gè)指針的地址值相同
④我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
將esp的值減少0E4h(也就是將esp指針上移)
⑤我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
實(shí)現(xiàn)步驟同上,從棧頂壓入三個(gè)元素,分別為ebx、esi、edi(我們暫時(shí)不用理會(huì)這三個(gè)寄存器)
⑥我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
我們勾選lea(load effective address)加載有效地址,將ebp-0E4h這個(gè)地址上存儲(chǔ)的內(nèi)容加載到edi中
⑦我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
mov操作,將"39h"存儲(chǔ)到ecx中;將"0CCCCCCCCh"存儲(chǔ)到eax中
⑧我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
從edi開始,向下39h個(gè)內(nèi)存空間的數(shù)據(jù)全部轉(zhuǎn)化為0CCCCCCCCh
引言問題的解決:
在這里我們回顧初學(xué)C語言時(shí),我們當(dāng)時(shí)創(chuàng)建一個(gè)變量,卻沒有對(duì)其賦值時(shí),或者在打印字符串時(shí),沒有找到’/0’時(shí),我們卻將其打印,得到結(jié)果中含有"燙燙燙"的字樣,這種情況的出現(xiàn),其實(shí)就是打印的初始化值"0CCCCCCCCh"
⑨我們對(duì)main函數(shù)的反匯編語言進(jìn)行分析:
mov操作的執(zhí)行,將"14h"存儲(chǔ)到ebp-8的位置、將"0Ah"存儲(chǔ)到ebp-4h的位置、將"0"存儲(chǔ)到ebp-20h的位置
①我們對(duì)Add函數(shù)的反匯編語言進(jìn)行分析:
將ebp-14h位置上的值移動(dòng)到eax的位置上,即將ebp-14h位置上的值傳遞給eax;
壓棧操作,將eax從棧頂壓入;
將將ebp-8位置上的值移動(dòng)到ecx的位置上,即將ebp-8位置上的值傳遞給ecx;
壓棧操作,將ecx從棧頂壓入;
同時(shí)這里操作,也是我們Add函數(shù)傳參的操作
②我們對(duì)Add函數(shù)的反匯編語言進(jìn)行分析:
call操作的執(zhí)行,我們會(huì)壓棧壓入call指令的下一條指令的地址
這一步執(zhí)行的原因,是當(dāng)我們Add函數(shù)執(zhí)行完之后, 我們返回結(jié)束時(shí),要繼續(xù)執(zhí)行我們的下一條指令,所以我們進(jìn)行記錄我們Add函數(shù)的下一條指令的地址
我們?cè)谶@里按F11進(jìn)入Add函數(shù),顯示如下:
我們對(duì)Add函數(shù)的反匯編語言進(jìn)行分析:
當(dāng)我們進(jìn)入Add函數(shù)之后,我們發(fā)現(xiàn)Add函數(shù)和main函數(shù)步驟相同,需要先為Add函數(shù)進(jìn)行初始化,ebp、esp的函數(shù)棧幀維護(hù)等,也就是需要?jiǎng)?chuàng)建Add函數(shù)所需要的棧幀,數(shù)據(jù)類型初始化。
①壓棧,壓入ebp,其中這個(gè)ebp就是當(dāng)前main函數(shù)的ebp
②mov操作,將esp的值傳遞給ebp,那么ebp就指向當(dāng)前的esp指向的位置
③sub操作,將esp減去0CCh,使esp指針上移;push操作,壓棧壓入ebx、esi、edi三個(gè)元素
④lea、mov、mov、rep stos的執(zhí)行步驟與main函數(shù)的初始化步驟相同
這時(shí),我們對(duì)代碼的內(nèi)容近一步分析
這里的兩個(gè)地址,我們通過圖解分析,可以得出,之前的ecx、eax就是實(shí)現(xiàn)我們的傳參步驟,分別為形參a’,b’;
⑤此時(shí),我們將ebp+8位置的值傳遞給eax,此時(shí)eax = 20;然后再將ebp+0Ch位置的值添加到eax中,此時(shí)eax = 30;再然后,將eax的值傳遞到ebp-8的位置;
引言問題的解決:
形參是實(shí)參的一份臨時(shí)拷貝,當(dāng)我們?cè)谶€沒有在main函數(shù)中執(zhí)行到Add()函數(shù)時(shí),我們已將b,a的數(shù)值壓棧進(jìn)入棧中,而這兩個(gè)壓棧進(jìn)入棧中的數(shù)據(jù),就是b、a的一份臨時(shí)拷貝,當(dāng)我們執(zhí)行到Add函數(shù)時(shí),我們就找回了之前壓棧存儲(chǔ)的b、a的值,所以我們說形參是實(shí)參的一份臨時(shí)拷貝
當(dāng)我們?cè)诤瘮?shù)中改變這份臨時(shí)拷貝的數(shù)值時(shí),對(duì)我們?cè)镜膶?shí)參并不會(huì)改變,因?yàn)楹瘮?shù)中改變的只是實(shí)參的一份臨時(shí)拷貝,所以我們說值傳遞的形參改變,并不會(huì)改變實(shí)參
⑥將ebp-8的值傳遞給eax中
⑦pop操作,將棧頂元素彈走;這三行代碼的執(zhí)行效果為,依次將棧頂?shù)脑貜椀絜di寄存器中、esi寄存器中,ebx寄存器中
此時(shí)的棧頂情況如圖:
即將edi元素彈回,存儲(chǔ)到edi寄存器中,esi、ebx同理。
執(zhí)行三次pop操作之后
此時(shí),我們的Add函數(shù)的任務(wù)已經(jīng)執(zhí)行結(jié)束,獲得的return值也已經(jīng)存儲(chǔ)到eax中,那么這個(gè)時(shí)候,Add函數(shù)的函數(shù)棧幀就沒有存在的必要了,我們需要對(duì)這一段空間進(jìn)行回收
⑧讓esp指針指向ebp的位置
⑨將當(dāng)前棧頂?shù)脑貜棾觯瑥椀絜bp寄存器中
而當(dāng)前的棧頂元素存儲(chǔ)的內(nèi)容就是main函數(shù)的ebp,那么效果就相當(dāng)于
此時(shí),Add函數(shù)的棧幀已經(jīng)收回,我們又回到了main函數(shù)的棧幀中
⑩前面的過程經(jīng)歷之后,我們的call指令就已經(jīng)執(zhí)行完畢,但我們?nèi)稳恍枰^續(xù)call函數(shù)之后的步驟,而此時(shí)ret操作的執(zhí)行,就是讓我們從call指令執(zhí)行完之后,返回到我們之前存儲(chǔ)的call指令的下一個(gè)指令地址的地方。繼續(xù)執(zhí)行call指之后的指令。
現(xiàn)在我們又回到了call指令之后此時(shí)main函數(shù)指針中的形參20、10就沒有用處了,那么我們就要將這兩個(gè)空間進(jìn)行回收
將"esp"+8,即就是將esp指針下移八個(gè)字節(jié)
此時(shí),也就是形參銷毀的真實(shí)時(shí)刻
將eax的值傳遞給ebp-20h中
我們回顧main函數(shù)中的棧幀
那么將eax的值傳遞給ebp-20h中,則
這里我們可以得出,當(dāng)我們調(diào)用返回函數(shù)時(shí),返回值都是先存放到寄存器中,然后當(dāng)我們真的返回到調(diào)用函數(shù)中時(shí),再從寄存器中讀取這個(gè)返回值
到此,余下的部分關(guān)于main函數(shù)的棧幀空間的銷毀與Add函數(shù)相同,就不在此敘述了,執(zhí)行的步驟與前面分析內(nèi)容形似。
以上就是我對(duì)函數(shù)中棧幀的創(chuàng)建與銷毀的個(gè)人理解
上述內(nèi)容如果有錯(cuò)誤的地方,還麻煩各位大佬指教【膜拜各位了】【膜拜各位了】
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/120822.html
摘要:這里分塊講解六函數(shù)棧幀的銷毀過程一解析的作用是將棧頂?shù)臄?shù)據(jù)彈出,彈出數(shù)據(jù)儲(chǔ)存到相應(yīng)寄存器中。 ?前言? 讀完這篇博客,你可以明白什么? ①局部變量到底是怎么在棧上創(chuàng)建的? ②為什么局部變量不初始化為隨機(jī)值? ③函數(shù)是怎么傳參的?傳參的先后順序是什么? ④形參和實(shí)參是什么關(guān)系? ⑤函數(shù)調(diào)用是怎...
摘要:目錄前言由于作者水平有限,文章難免存在謬誤之處,敬請(qǐng)讀者斧正,俚語成篇,懇望指教前言由于作者水平有限,文章難免存在謬誤之處,敬請(qǐng)讀者斧正,俚語成篇,懇望指教作者新曉故知作者新曉故知那些代碼背后的故事那些代碼背后的故事通過 目錄 前言:●由于作者水平有限,文章難免存在謬誤之處,敬請(qǐng)讀者斧正,俚...
摘要:函數(shù)棧幀的銷毀匯編語言了解函數(shù)傳參函數(shù)返回值如何返回函數(shù)中變量如何初始化和賦值函數(shù)執(zhí)行結(jié)束后系統(tǒng)進(jìn)行了什么操作 文章目錄 一、什么是函數(shù)棧幀 1.寄存器2.函數(shù)棧幀3.棧幀的作用和維護(hù)4.棧幀結(jié)構(gòu)二、函數(shù)棧幀的創(chuàng)建? 1.匯編2.main函數(shù)3.Add函數(shù)的創(chuàng)建三、函數(shù)...
摘要:同時(shí),和所指示的位置會(huì)隨著函數(shù)棧幀的創(chuàng)建和銷毀而不斷的發(fā)生改變。再次執(zhí)行函數(shù)棧幀的創(chuàng)建操作。函數(shù)的返回值會(huì)存在一個(gè)寄存器中當(dāng)函數(shù)棧幀釋放后,返回值不會(huì)隨之消失。二函數(shù)棧幀的銷毀將一些函數(shù)調(diào)用中使用的寄存器彈出棧。 ...
閱讀 2910·2021-10-14 09:42
閱讀 1256·2021-09-24 10:32
閱讀 2973·2021-09-23 11:21
閱讀 2851·2021-08-27 13:10
閱讀 3343·2019-08-29 18:41
閱讀 2206·2019-08-29 15:16
閱讀 1217·2019-08-29 13:17
閱讀 900·2019-08-29 11:22