91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

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

函數堆棧調用過程

發布時間:2020-06-30 13:55:33 來源:網絡 閱讀:988 作者:這里有酒 欄目:編程語言

從內存的角度詳細的分析C語言中的函數調用過程:

首先寫一個測試用的代碼:


#include <stdio.h>

int add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 1, b = 2;
	int c = 0;

	c = add(a, b);

	return 0;
}

這是一個簡單的的求和函數。


其次,讓我們確定一下,程序是從哪里開始運行的:

調試程序,按一下F10(博主用的VS2013),

進入main函數:

函數堆棧調用過程

然后進調試--->窗口--->調用堆棧(用來顯示函數的調用關系)。

函數堆棧調用過程

發現正在調用main這個函數,但現在我想知道是誰在調用main函數,F10一路走到return 0,接著換F11(逐語句調試),然后會發現,main函數返回后,我們來到了這里:

函數堆棧調用過程

再看看此時的調用堆棧:

函數堆棧調用過程

直接來看,現在運行的函數是__tmainCRTStartup(),這個函數又被mainCRTStratup()調用,而我們剛剛是從main()函數返回來的,所以,main()函數是由__tmainCRTStartup()這個函數調用的。

了解了main()函數是被誰調用后,我們可以進一步分析這其中的細節了!

現在重新F10進入調試,到這一步:

函數堆棧調用過程

進入main()函數后還沒有執行任何一條語句,我們 右擊-->轉到反匯編:

函數堆棧調用過程

看到了匯編語言的代碼,圖中的ebp和esp是什么東西呢?我們知道,調用函數的時候操作系統要給這個函數分配一段內存空間,之前又說了main()函數是由—__tCRTStartup()函數調用的,所以請看:

函數堆棧調用過程

mainCRTStratup()函數調用__tmainCRTStra()函數的時候就會從棧上為__tmainCRTStra()分配類似圖中這么一塊空間,把這塊空間叫做棧幀。我們知道棧是由高地址向低地址擴展的。其中ebp叫做棧底指針,esp叫做棧頂指針(當然也有其它叫法)。ebp,esp本身是一個寄存器,其中存放了地址時,我們就稱之為指針!

現在再來看匯編程序:

函數堆棧調用過程

按一下F10執行第一條語句,箭頭指向下一條語句,變成這樣:

函數堆棧調用過程

(和我們在外邊的調試是一樣的)這句 push ebp 就是將ebp中的值進行壓棧,而此時ebp存放的是系統分給__tmainCRTStartup()函數的空間的起始地址。因為我們現在要調用main()函數了,所以當然要先把__tmainCRTStartup()函數的運行狀態保存下來,這樣main()函數才能返回的時候才能找得到!push是在棧頂進行的,所以,push之后,esp要向上移動:

函數堆棧調用過程


剛剛說了,棧是由高地址向低地址擴展的,所以這個push操作應該是對esp進行一個減操作,具體見了多少,可以在內存里查一查:

先看一下push之前esp的的值:

函數堆棧調用過程

函數堆棧調用過程

esp的當前值為0x00ABFA30,代表它指向0x00ABFA30這個地址代表的內存。

再看一下push之后esp的值發生了什么變化:

函數堆棧調用過程

變成了0x00ABFA2C,差了4個字節,就是放進去的地址的大小。

然后繼續執行下一條語句: mov         ebp,esp   

即把esp的值賦給ebp,這樣,ebp也就指向了現在esp的位置,如下圖:

函數堆棧調用過程

 接著又執行語句:sub         esp,0E4h  

即將esp的值減去E4h,所以esp向上移動了E4h個位置(相當于申請了這么大的一塊空間),新申請的這塊空間就給main()用了。如下:

函數堆棧調用過程


函數堆棧調用過程

接下來緊接著三條push語句將后面要用到的寄存器中原來的值存儲起來,等我們借用完寄存器后再給人家pop回去,不管它,這里esp再向上移動三次。

函數堆棧調用過程

(ps:圖片太大,所以只截了當前要用到的)

緊接著的四條語句共同完成一個任務,就是將圖中最大長方形區域初始化為0CCCCCCCh(你經常看到的:燙燙燙燙......)

 第一句:lea         edi,[ebp-0E4h]  
就是將ebp減去E4h的值賦給edi,這個E4h是不是很眼熟呢?它就是我們上一步分配給main()的空間的大小,即edi指向了3次push之前的esp的位置;

第二句:mov         ecx,39h 
把39h放在ecx中(充當了計數器)

第三句:mov         eax,0CCCCCCCCh 

把要初始化的數據寫入eax

最后一句:rep stos    dword ptr es:[edi]

循環的從低地址(ebp-0E4h)向高地址(ebp)寫0CCCCCCCCh,循環了39h次!

我們在執行之前轉到內存中看一下:

先查找ebp:

函數堆棧調用過程

函數堆棧調用過程

(我往下拖了一點,左下角的光標處的地址就是ebp當前值0z00ABFA2C)

四條語句執行后:

函數堆棧調用過程

相應的位置已經被初始化為0CCCCCCCh,其它部分是亂碼(此時ebp值為0x00ABFA2C,它之上的一段空間是分配給main()的)

程序繼續往下執行:

函數堆棧調用過程

mov         dword ptr [ebp-8],1  在ebp-8h的位置放一個1,
mov         dword ptr [ebp-14h], 2 在ebp-14h的位置放一個2

即分別創建了a,b兩個變量,如圖:

函數堆棧調用過程

接著創建c:

函數堆棧調用過程

此時我們的內存分配變成了這樣:

函數堆棧調用過程

然后到了這里,調用add()準備工作:

函數堆棧調用過程

mov         eax,dword ptr [ebp-14h]    是把ebp-14h位置的值放入eax(此時ebp-14h的值是我們的變量b的值),然后:push        eax , 即eax壓棧;

同理,mov         ecx,dword ptr [ebp-8]   把ebp-8位置的值放入ecx,然后ecx壓棧。如下(傳遞形參給x和y):

函數堆棧調用過程


程序到這:

函數堆棧調用過程

在匯編里我們用call調用一個函數(_add是一個標號,它代表了一個地址,是add()函數的首地址),而call在執行的同時,會把它下一條指令的地址(就是圖中的00D1450)push到main()的棧楨中去,以便add()執行完后返回的時候還可以找到程序當初執行到了哪里,然后接著執行。

為了證明這一點,我們先查看一下esp所指向內存的值:

函數堆棧調用過程

然后F11跟進去到這里:

函數堆棧調用過程

再查看esp所指內存:

函數堆棧調用過程

可以看到esp的位置發生了改變,此時內存中的值 50 14 0d 00 是不是很像剛剛的call語句下一條指令地址呢?對它就是00 0d 14 50 的小端字節序,這里不再解釋小端字節序,只需理解它是內存中字節存儲的一種方式,有興趣的可以查看:http://blog.csdn.net/qq_33724710/article/details/51056542

棧楨分配圖變成了這樣:

函數堆棧調用過程

接著F11執行剛剛的jmp語句:

函數堆棧調用過程

歷盡千辛萬苦終于進入add()!現在貼出來的這幾句代碼就和我們剛剛進入main()函數的語句大同小異了。

  push        ebp  //ebp壓棧
  mov         ebp,esp  //ebp指向esp所指
  sub         esp,0CCh  //esp - 0CCh, 開辟了新的棧楨
  push        ebx  //3個push,照舊不管它
  push        esi  
  push        edi  
  lea         edi,[ebp-0CCh]  //初始化燙燙燙燙......  
  mov         ecx,33h  
  mov         eax,0CCCCCCCCh  
  rep stos    dword ptr es:[edi] 

然后到這里:

函數堆棧調用過程

給ebp-8處放了個0,就是創建z啦!

函數堆棧調用過程

再接著到這里:

函數堆棧調用過程

eax,dword ptr [ebp+8]  //注意是加了8,取出的是我們之前傳遞進來的形參值1,放到eax
add         eax,dword ptr [ebp+0Ch]  //取epb+0Ch,取出的是我們之前傳遞進來的形參值2,加到eax
dword ptr [ebp-8],eax  //再把求和后的值eax賦給epb - 8的位置,就是z嘍! 

函數堆棧調用過程

程序執行到這,準備返回main()了:

函數堆棧調用過程

因為z是個臨時變量,出了add()就會銷毀,要返回z的值,就要把它的值放進寄存器:

mov         eax,dword ptr [ebp-8]  //epb-8找到的就是z,賦給eax
 pop         edi  //連續三個pop,之前連續三個push我們沒管它,現在仍然不管它
 pop         esi  
 pop         ebx  
3次pop后,esp高地址處移動了3個單位:

函數堆棧調用過程

雖然esp上邊的空間還在,但是已經不屬于當前的棧楨了,相當于釋放掉了!

然后:

  mov         esp,ebp  //esp指向當前ebp
  pop         ebp  //main()起始地址賦給給ebp,esp往高地址處移動一次

所以變成這樣:

函數堆棧調用過程

最后執行ret,程序回到這里:

函數堆棧調用過程
看見了沒,ret指令自動取出了call的下一條語句地址(ret自動執行了pop,esp又往高地址處移動了一次)賦給了PC(PC總是指向下一條要執行的語句)。

接著的add         esp,8  使esp繼續往高地址方向移動,并跳過1,2兩個參數,如下:

函數堆棧調用過程

函數堆棧調用過程

mov         dword ptr [ebp-20h],eax  //還記得eax嗎,當初我們把求和的結果,即 z 的值賦給了它,ebp-20h依然是當初的c

現在,我們要的結果已經賦給 c 了!

函數堆棧調用過程

  xor         eax,eax  //eax沒用了,異或eax,清零

  pop         edi  //又是連續3個pop
  pop         esi  
  pop         ebx  
  add         esp,0E4h  //oE4h,當出開辟的main()棧楨的大小,現在釋放掉
  cmp         ebp,esp  //不管它
  call        000D113B  //不管它
  mov         esp,ebp  //釋放main()棧楨
  pop         ebp  //ebp指向__tmainCRTStartup()起始地址,esp下移
  ret  //返回到__tmainCRTStartup()

__tmainCRTStartup()和mainCRTStart()里邊的過程就不在分析了!


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

夹江县| 平定县| 乐昌市| 松江区| 杨浦区| 承德县| 什邡市| 青铜峡市| 奉新县| 永年县| 阳信县| 招远市| 江永县| 奎屯市| 甘南县| 仙居县| 六安市| 许昌市| 项城市| 山东省| 孟州市| 昌乐县| 临颍县| 依兰县| 扎囊县| 黔西县| 漳州市| 垦利县| 通州区| 酒泉市| 许昌县| 土默特左旗| 益阳市| 格尔木市| 桃源县| 齐齐哈尔市| 苏尼特右旗| 同心县| 崇仁县| 台江县| 子长县|