主頁 > 軟體設計 > 淺談函式堆疊幀

淺談函式堆疊幀

2021-08-08 08:06:40 軟體設計

作者的碼云地址:https://gitee.com/dongtiao-xiewei

后續作者會更新力扣的每日一題系列,原代碼會全部上傳碼云,推薦關注哦~筆芯~

目錄

記憶體管理和函式堆疊幀

一些準備作業

main函式堆疊幀

push壓堆疊操作

什么是壓堆疊?出堆疊?

ebp和esp

main函式空間的開辟

變數的創建以及傳參

創建變數

傳參(形式引數的創建):

進入函式內部

Add函式堆疊幀開辟

引數傳遞與回傳

重新回到main函式

總結與提示


記憶體管理和函式堆疊幀

我們知道計算機分配記憶體時主要分為以下的幾個區域

我們也知道,函式是在堆疊區上開辟空間的,

每一次的函式呼叫(包括main函式,自定義函式等)都會在堆疊區上開辟足夠大的空間 ,用于本次函式中的內部操作(資料保護,區域變數控制),這塊空間我們稱為函式堆疊幀,

這篇文章可以為大家解答以下問題:

  1. 區域變數怎么創建?
  2. 為什么區域變數具有隨機值?
  3. 函式怎么傳參?
  4. 形參與實參的關系?
  5. 函式的呼叫怎么進行?
  6. 函式怎么回傳值?

由于文章較長,而且涉及反匯編,也就是匯編語言的知識,如果讀者想知道答案,可以直接看堆疊區圖和總結與提示板塊,

我們的研究方式:

  1. VS2019
  2. 撰寫一段簡單的程式
  3. 在除錯程序中查看反匯編程序

正文分界線


正文分界線

一些準備作業

首先在VS2019中敲出以下代碼

//test.c
int Add(int x,int y)
{
    int z=0;
    z=x+y;
    return z;
    
}
int main()
{
    int a=0;
    int b=0;
    int c=0;
    c=Add(a,b);
    printf("%d\n",c);
    return 0;
}

按F10開始逐程序除錯,F11可以逐陳述句除錯(也就是進入函式內部)

然后點擊反匯編選項,將會觀察到以下代碼

這是這個代碼全部的反匯編代碼

int main()
{
000818B0  push        ebp  
000818B1  mov         ebp,esp  
000818B3  sub         esp,0E4h  
000818B9  push        ebx  
000818BA  push        esi  
000818BB  push        edi  
000818BC  lea         edi,[ebp-24h]  
000818BF  mov         ecx,9  
000818C4  mov         eax,0CCCCCCCCh  
000818C9  rep stos    dword ptr es:[edi]  
000818CB  mov         ecx,offset _2A160C42_test@c (08C003h)  
000818D0  call        @__CheckForDebuggerJustMyCode@4 (08131Bh)  
    int a = 0;
000818D5  mov         dword ptr [a],0  
    int b = 0;
000818DC  mov         dword ptr [b],0  
    int c = 0;
000818E3  mov         dword ptr [c],0  
    c = Add(a, b);
000818EA  mov         eax,dword ptr [b]  
000818ED  push        eax  
000818EE  mov         ecx,dword ptr [a]  
000818F1  push        ecx  
000818F2  call        _Add (0810B4h)  
000818F7  add         esp,8  
000818FA  mov         dword ptr [c],eax  
    printf("%d\n", c);
000818FD  mov         eax,dword ptr [c]  
00081900  push        eax  
00081901  push        offset string "%d\n" (087B30h)  
00081906  call        _printf (0810D2h)  
0008190B  add         esp,8  
    return 0;
0008190E  xor         eax,eax  
}
00081910  pop         edi  
00081911  pop         esi  
00081912  pop         ebx  
00081913  add         esp,0E4h  
00081919  cmp         ebp,esp  
0008191B  call        __RTC_CheckEsp (081244h)  
00081920  mov         esp,ebp  
00081922  pop         ebp  
00081923  ret  

這里筆者只講與本文相關,也就是printf以前的匯編指令,

注意:由于編譯器的不同,測驗結果可能與本文不同,此文僅做參考~

main函式堆疊幀

push壓堆疊操作

什么是壓堆疊?出堆疊?

我們知道堆疊區的使用規則是:先使用高地址,再使用低地址,回收也是先回收最頂上的地址,也就是低地址,再回收高地址,也就是先進后出,

這樣我們先分析以下幾行反匯編代碼:

000818B0  push        ebp  
000818B1  mov         ebp,esp  
000818B3  sub         esp,0E4h 

哦對了,還沒為大家介紹ebp和esp是啥,馬上補上~

ebp和esp

首先甩出定義:是計算機中可以存放地址的暫存器,用于維護函式堆疊幀

那么可能大家又好奇了,啥是維護函式堆疊幀啊?

我們已經知道,ebp和esp暫存器是用于存放地址的,那么是存放哪里的地址呢?

后文大家可以找到答案,不過還是先把結論甩在這兒吧:

  1. esp用于存放堆疊底指標,指向被維護函式最高地址
  2. ebp用于存放堆疊頂指標,指向被維護函式最低地址

所以,位于esp和ebp之間的空間,也就是記憶體為此次函式分配的空間,也就是函式堆疊幀啦~

解釋完了,我們接下來執行這幾句命令

main函式空間的開辟

這是執行到mov指令時的esp和ebp的地址

可以知道,第一步push指令將預開辟的main函式的最高地址存放至ebp中,第二步將ebp存放的地址同時存放到esp中

接下來執行這幾條指令

000818B3  sub         esp,0E4h  
000818B9  push        ebx  
000818BA  push        esi  
000818BB  push        edi  
000818BC  lea         edi,[ebp-24h]    
000818C9  rep stos    dword ptr es:[edi]

第一行,我們將esp往上移動,也就是往低地址移動0E4h這么大的位元組

第二行,將ebx,esi,edi,分別執行壓堆疊操作

第四行,LEA(load effective address)讀取此時esp的地址,為后文做下鋪墊

最后一行,將此時ebp和esp之間的空間全部初始化為cc cc cc cc

這里是main函式空間開辟的關鍵步驟的堆疊區圖

這里也可以順便解釋為什么變數沒有初始化是隨機值的原因~

不同編譯器初始化的值不一樣,但VS2019是cc cc cc cc,對應漢字也就是大家熟知的燙燙燙,,,

至此,main函式空間開辟完畢~

變數的創建以及傳參

創建變數

由以下幾段反匯編代碼實作

    int a = 0;
000818D5  mov         dword ptr [a],0  
    int b = 0;
000818DC  mov         dword ptr [b],0  
    int c = 0;
000818E3  mov         dword ptr [c],0 

以下是創建變數時的堆疊區圖

傳參(形式引數的創建):

先甩結論:形式引數創建在main函式的頂部,并不在函式內部創建

以下幾行代碼可以解釋函式傳參

000818EA  mov         eax,dword ptr [b]  
000818ED  push        eax  
000818EE  mov         ecx,dword ptr [a]  
000818F1  push        ecx  

由于區域變數有先進后出,所以為了確保a能夠先被呼叫,a將比b后創建,這樣確保了a會被b先使用,也就是引數是從右向左傳遞的

這幾行代碼反應成漢語就是:

將b的值存進eax中并進行壓堆疊操作,再對a進行相同操作

當然,進行了這步操作,為了能維護函式,還需要將ecx最頂端的地址存放在esp中

然后接下來一條指令

000818F2  call        _Add (0810B4h) 

將add的地址存放在頂部,這次一個伏筆,后文會提到~

進入函式內部

這是函式內部的反匯編代碼

int Add(int x, int y)
{
00081770  push        ebp  
00081771  mov         ebp,esp  
00081773  sub         esp,0CCh  
00081779  push        ebx  
0008177A  push        esi  
0008177B  push        edi  
0008177C  lea         edi,[ebp-0Ch]  
0008177F  mov         ecx,3  
00081784  mov         eax,0CCCCCCCCh  
00081789  rep stos    dword ptr es:[edi]  
0008178B  mov         ecx,offset _2A160C42_test@c (08C003h)  
00081790  call        @__CheckForDebuggerJustMyCode@4 (08131Bh)  
    int z = 0;
00081795  mov         dword ptr [z],0  
    z = x + y;
0008179C  mov         eax,dword ptr [x]  
0008179F  add         eax,dword ptr [y]  
000817A2  mov         dword ptr [z],eax  
    return z;
000817A5  mov         eax,dword ptr [z]  

}
000817A8  pop         edi  
000817A9  pop         esi  
000817AA  pop         ebx  
000817AB  add         esp,0CCh  
000817B1  cmp         ebp,esp  
000817B3  call        __RTC_CheckEsp (081244h)  
000817B8  mov         esp,ebp  
000817BA  pop         ebp  
000817BB  ret

Add函式堆疊幀開辟

這段代碼與main函式同理,為Add開辟函式堆疊幀

00081770  push        ebp  
00081771  mov         ebp,esp  
00081773  sub         esp,0CCh  
00081779  push        ebx  
0008177A  push        esi  
0008177B  push        edi  
0008177C  lea         edi,[ebp-0Ch]  
0008177F  mov         ecx,3  
00081784  mov         eax,0CCCCCCCCh  
00081789  rep stos    dword ptr es:[edi]  
0008178B  mov         ecx,offset _2A160C42_test@c (08C003h)  
00081790  call        @__CheckForDebuggerJustMyCode@4 (08131Bh)  

引數傳遞與回傳

    int z = 0;
00081795  mov         dword ptr [z],0  
    z = x + y;
0008179C  mov         eax,dword ptr [x]  
0008179F  add         eax,dword ptr [y]  
000817A2  mov         dword ptr [z],eax  
    return z;
000817A5  mov         eax,dword ptr [z] 

由這幾段的代碼得出結論,計算可以直接在eax,ecx,也就是臨時變數上進行

最后將得出的結果放入eax中

000817A8  pop         edi  
000817A9  pop         esi  
000817AA  pop         ebx  
000817AB  add         esp,0CCh  
000817B1  cmp         ebp,esp  
000817B3  call        __RTC_CheckEsp (081244h)  
000817B8  mov         esp,ebp  
000817BA  pop         ebp  
000817BB  ret

1-3行代碼涉及將最頂上三個元素彈出

而其中mov esp,ebp(將ebp的地址存盤到esp地址中)這條指令,成功實作了將Add函式堆疊幀回收

pop ebp 指令將ebp彈入原main最下部,可以繼續維護原main函式

而最后的ret指令,可以實作回到main函式中call指令發出的地方,也就是存放地址的地方,可以實作整個程式能夠繼續按照流程繼續下去,

重新回到main函式

000818F7  add         esp,8  
000818FA  mov         dword ptr [c],eax  

第一步,將原來為臨時變數x,y開辟的空間回收

第二步,將eax中存放的z的值傳給c

至此,成功實作z的回傳

總結與提示

  1. 區域變數在main函式堆疊幀中創建,資料型別決定了記憶體修改權限大小
  2. 不同的編譯器初始化放進的值不一樣,從而導致了不初始化將會出現隨機值的情況
  3. 函式傳參是在main最頂上執行壓堆疊操作,從右到左傳參,并沒有創建在函式內部
  4. 形參是實參的一份臨時拷貝
  5. 函式執行必須先創建函式堆疊幀,再進行函式操作
  6. 函式若要回傳值,需要先將回傳值暫時存在暫存器中,方便函式空間被回收后回傳

本期內容到此結束啦!由于作者水平有限,文章中如有不足與疏漏之處在所難免,希望各位大佬提出你們寶貴的意見!筆芯~

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292392.html

標籤:其他

上一篇:我用 140 行代碼,帶你看一場流星雨?

下一篇:函式堆疊幀的創建和銷毀

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more