C語言:函數棧幀的創建和銷毀

目錄

  • 1.什么是函數棧幀
  • 2.理解函數棧幀能解決什么問題
  • 3.函數棧幀的創建和銷毀的過程解析
    • 3.1 什么是棧
    • 3.2 認識相關寄存器和匯編指令
    • 3.3 解析函數棧幀的創建和銷毀過程
      • 3.3.1 準備環境
      • 3.3.2 函數的調用堆棧
      • 3.3.3 轉到反匯編
      • 3.3.4 函數棧幀的創建和銷毀

1.什么是函數棧幀

在寫C語言代碼的時候,我們經常會把一個獨立的功能抽象成函數,C程序是以函數為基本單位的,那么函數又是如何調用的呢?函數的參數是怎樣傳遞的呢?這些答案都可以在函數棧幀中尋找

函數棧幀(stack frame):函數調用過程中在程序的調用棧(call stack)所開辟的空間,這些空間是用來存放:

  • 函數參數和函數返回值
  • 臨時變量(包括函數的非靜態的局部變量以及編譯器自動產生的其他臨時變量)
  • 保存上下文信息(包括用來維護函數調用前后的寄存器)

2.理解函數棧幀能解決什么問題

只要理解好函數棧幀就可以對一下問題有額外的理解:

  • 局部變量是如何創建的?
  • 為什么局部變量不初始化時為隨機值?
  • 函數調用時形參的傳遞的順序是怎樣的?
  • 函數的形參和實參的聯系是怎樣的?
  • 函數的返回值是如何帶回來的?

3.函數棧幀的創建和銷毀的過程解析

3.1 什么是棧

棧(stack)是現代計算機程序中最為重要的概念之一,幾乎每一個程序都要用到棧,沒有棧就沒有函數,沒有局部變量,更沒有更正語言的橋接

在經典的計算機科學中,棧被定義為一種特殊的容器,用戶可以將數據壓入棧中(該操作被稱為壓棧:push),也可以將已經壓入棧中的數據彈出(出棧:pop),但是棧這個容器遵守一條規則:先進后出

在計算機系統中,棧則是一個具有以上屬性的動態內存區域,程序可以將數據壓入棧中,也可以將棧彈出,在經典的操作系統中,棧總是向下增長(由高地址到低地址)的,在我們常見的i386或者x86-64下,棧頂由esp的寄存器定位

3.2 認識相關寄存器和匯編指令

<1.相關寄存器

  • eax:通用寄存器,保留臨時數據,常用于返回值
  • ebx:同樣寄存器,保留臨時數據
  • ebp:棧底寄存器
  • esp:棧頂寄存器,與ebp共同維護當前的函數棧幀
  • eip:指令寄存器,保存當前指令的下一條指令的地址

<2.相關匯編命令

  • mov:數據轉移指令,將后面的數據賦值給前面的數據
  • push:數據入棧,同時esp寄存器也要發生改變
  • pop:數據彈出指定位置,同時esp也要發生改變
  • sub:減法命令
  • add:加法命令
  • call:函數調用,1.壓入返回地址 2.轉入目標函數
  • jump:通過修改eip,轉入目標函數,進行調用
  • ret:恢復返回地址,壓入eip,類似pop eip命令

3.3 解析函數棧幀的創建和銷毀過程

3.3.1 準備環境

為了更好地觀察函數棧幀的整個過程,需要先關閉一些選項以免受到干擾:
在這里插入圖片描述
在這里插入圖片描述

3.3.2 函數的調用堆棧

這里我們寫一段簡單的代碼,并將代碼一條一條拆解處理足夠好觀察內部的細節

注意:函數棧幀的創建和銷毀過程,在不同的編譯器的實現方法大同小異,但大體的邏輯層次是不會差很多的,本次演示用的是VS2019環境

演示代碼:

#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("%d\n", c);return 0;
}

在VS2019環境下,按F10進入調試,打開窗口的調用堆棧:
在這里插入圖片描述
調用堆棧是用來反饋函數調用邏輯的

然后繼續按F10(按完整個主函數),進入界面:
在這里插入圖片描述

我們會發現main函數也是被調用的,這里可以清晰地觀察到:invoke_main函數調用了main函數,至于是什么函數調用了invoke_main就不再考慮了

3.3.3 轉到反匯編

按F10調試到main函數的第一行,右擊鼠標轉到反匯編
注意:這里調試出來的地址是由系統自動分配的,所以每一次進去調試出來的地址都是不同的


int main()
{
//main函數的函數棧幀的創建
004C1820  push        ebp  
004C1821  mov         ebp,esp  
004C1823  sub         esp,0E4h  
004C1829  push        ebx  
004C182A  push        esi  
004C182B  push        edi  
004C182C  lea         edi,[ebp-24h]  
004C182F  mov         ecx,9  
004C1834  mov         eax,0CCCCCCCCh  
004C1839  rep stos    dword ptr es:[edi]  //main函數中的核心代碼int a = 10;
004C183B  mov         dword ptr [ebp-8],0Ah  int b = 20;
004C1842  mov         dword ptr [ebp-14h],14h  int c = 0;
004C1849  mov         dword ptr [ebp-20h],0  c = Add(a, b);
004C1850  mov         eax,dword ptr [ebp-14h]  
004C1853  push        eax  
004C1854  mov         ecx,dword ptr [ebp-8]  
004C1857  push        ecx//執行call指令會跳轉到Add函數內部
004C1858  call        004C10B4  
004C185D  add         esp,8  
004C1860  mov         dword ptr [ebp-20h],eax  printf("%d\n", c);
004C1863  mov         eax,dword ptr [ebp-20h]  
004C1866  push        eax  
004C1867  push        4C7B30h  
004C186C  call        004C10D2  
004C1871  add         esp,8  return 0;
004C1874  xor         eax,eax  }

調試至call指令,按住F11:

//Add函數的函數棧幀
int Add(int x, int y)
{
004C1760  push        ebp  
004C1761  mov         ebp,esp  
004C1763  sub         esp,0CCh  
004C1769  push        ebx  
004C176A  push        esi  
004C176B  push        edi  int z = 0;
004C176C  mov         dword ptr [ebp-8],0  z = x + y;
004C1773  mov         eax,dword ptr [ebp+8]  
004C1776  add         eax,dword ptr [ebp+0Ch]  
004C1779  mov         dword ptr [ebp-8],eax  return z;
004C177C  mov         eax,dword ptr [ebp-8]  
}
004C177F  pop         edi  
004C1780  pop         esi  
004C1781  pop         ebx  
004C1782  mov         esp,ebp  
004C1784  pop         ebp  
004C1785  ret  

3.3.4 函數棧幀的創建和銷毀

這里我們將拆解每一行匯編代碼:

//main函數棧幀的創建,在創建之前esp和ebp維護的是invoke_main的函數棧幀
004C1820  push        ebp  //將ebp寄存器的值進行壓棧,此時存放的是invoke_main的函數棧幀的ebp,esp-4
004C1821  mov         ebp,esp  //將esp中的值賦給ebp
004C1823  sub         esp,0E4h //將esp減去0eE4(十六進制的表示),此時的esp已經指向了一個新的區域用來維護main的函數棧幀
004C1829  push        ebx  //把ebx的值進行壓棧,esp-4
004C182A  push        esi  //把esi的值進行壓棧,esp-4
004C182B  push        edi  //把edi的值進行壓棧,esp-4
//上面3條指令保存了3個寄存器的值在棧區,這3個寄存器的在函數隨后執行中可能會被修改,所以先保存寄
//存器原來的值,以便在退出函數時恢復。004C182C  lea         edi,[ebp-24h]  //lea(load effective address)加載有效地址,將ebp-24h的地址放到edi中
004C182F  mov         ecx,9  //將9賦給ecx
004C1834  mov         eax,0CCCCCCCCh  //將0CCCCCCCCh賦給eax
004C1839  rep stos    dword ptr es:[edi]  
//從edi開始將以ecx的存儲數值為個數的4個字節的數據全部改為eax存儲的值
//dword:double word(雙字),一個字為2個字節,雙字就是4個字節

該段匯編的內存:
在這里插入圖片描述

解釋燙燙燙的產生:
在這里插入圖片描述
之所以得到了這些奇怪的漢字,是因為這里使用了未初始化的字符數組,就導致buf中存儲的就是上面的0CCCCCCCCh的值,而0xCCCC的漢字編碼就是“燙”

//main函數中的核心代碼int a = 10;
004C183B  mov         dword ptr [ebp-8],0Ah  //將0Ah(10)賦給ebp-8的地址處,對變量a初始化int b = 20;
004C1842  mov         dword ptr [ebp-14h],14h  //將14h(20)賦給ebp-14h的地址處,對變量b初始化int c = 0;
004C1849  mov         dword ptr [ebp-20h],0  //將0賦給ebp-20h的地址處,對變量c初始化//調用Add函數c = Add(a, b);
004C1850  mov         eax,dword ptr [ebp-14h]  //將ebp-14h地址處的值(20)存放在eax中,這里其實就是傳遞參數b
004C1853  push        eax  //把eax的值進行壓棧
004C1854  mov         ecx,dword ptr [ebp-8]  //將ebp-8地址處的值(10)存放在ecx中,這里是傳遞參數a
004C1857  push        ecx //把ecx的值進行壓棧

該段匯編的內存:

在這里插入圖片描述

//執行call指令會跳轉到Add函數內部,在跳轉之前會進行壓棧操作
004C1858  call        004C10B4  //把call指令的下一條的地址進行壓棧,esp-4,回調函數//Add函數的函數棧幀的創建
004C1760  push        ebp  //將main函數的ebp的值壓棧進行保存,esp-4
004C1761  mov         ebp,esp  //將esp的值賦給ebp
004C1763  sub         esp,0CCh  //將esp減去0CCh,esp開始維護新函數Add的函數棧幀
004C1769  push        ebx  //把ebx的值進行壓棧,esp-4
004C176A  push        esi  //將esi的值進行壓棧,esp-4
004C176B  push        edi  //將edi的值進行壓棧,esp-4
//Add函數中的核心代碼int z = 0;
004C176C  mov         dword ptr [ebp-8],0 //將0賦給ebp-8的地址處 z = x + y;
004C1773  mov         eax,dword ptr [ebp+8]  //將ebp+8地址處的值賦給eax
004C1776  add         eax,dword ptr [ebp+0Ch]  //把ebp+0Ch地址處的值加到eax中
004C1779  mov         dword ptr [ebp-8],eax  //將eax中的值賦到ebp-8的地址處return z;
004C177C  mov         eax,dword ptr [ebp-8]  //將ebp-8地址處的值賦給eax
}

該段匯編的內存:
在這里插入圖片描述
可以看出形參和實參的關系:形參是實參的一份臨時拷貝,對形參的修改并不會改變實參

004C177F  pop         edi  //把edi的值進行出棧,esp+4
004C1780  pop         esi  //把esi的值進行出棧,esp+4
004C1781  pop         ebx  //把ebx的值進行出棧,esp+4
004C1782  mov         esp,ebp  //將ebp的值賦給esp
004C1784  pop         ebp  //把ebp的值進行出棧,ebp此時又回到main函數的ebp,開始維護main函數的函數棧幀,esp+4
004C1785  ret //首先彈出棧頂的值,此時esp+4,并回到call指令的下一條地址處繼續執行代碼
//Add函數的函數棧幀銷毀

回到call指令的下一條指令:

004C185D  add         esp,8 //esp+8  
004C1860  mov         dword ptr [ebp-20h],eax  //將eax的值(存儲的就是Add函數的返回值)賦到ebp-20h的地址處printf("%d\n", c);
004C1863  mov         eax,dword ptr [ebp-20h]  //將ebp-20h地址處的值賦給eax
004C1866  push        eax  //將eax的值進行壓棧
004C1867  push        4C7B30h  //將4C7B30h進行壓棧
004C186C  call        004C10D2  //繼續回調函數
004C1871  add         esp,8  //esp+8return 0;
004C1874  xor         eax,eax  }

該段匯編的內存():
在這里插入圖片描述
小結:對于函數棧幀的創建和銷毀過程可以在VS上獨自進行反匯編代碼解析 + 內存和監視的觀察,理解該過程更有助于對代碼底層的東西了解的更深,此外,這里對于main函數的函數棧幀的銷毀不再解析,相信了解過Add函數的整個過程后便能明白,上述提出的問題也可以直接回答出

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/894856.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/894856.shtml
英文地址,請注明出處:http://en.pswp.cn/news/894856.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

25/2/6 <機器人基礎> 運動學中各連桿的變換矩陣求法

變換矩陣 機器人通常包含多個關節和連桿&#xff0c;每個關節和連桿都有自己的局部坐標系。變換矩陣能夠將一個點或向量從一個坐標系轉換到另一個坐標系&#xff0c;從而實現對機器人各個部件位置和姿態的統一描述 變換矩陣能夠將復雜的運動分解為旋轉和平移的組合。通過矩陣乘…

AllData數據中臺核心菜單十二:數據同步平臺

&#x1f525;&#x1f525; AllData大數據產品是可定義數據中臺&#xff0c;以數據平臺為底座&#xff0c;以數據中臺為橋梁&#xff0c;以機器學習平臺為中層框架&#xff0c;以大模型應用為上游產品&#xff0c;提供全鏈路數字化解決方案。 ?奧零數據科技官網&#xff1a;…

【FPGA】 MIPS 12條整數指令 【3】

實現乘除 修改框架 EX&#xff1a;實現帶符號乘除法和無符號乘除法 HiLo寄存器&#xff1a;用于存放乘法和除法的運算結果。Hi、Lo為32bit寄存器。電路描述與實現RegFile思想一致 仿真 代碼 DataMem.v include "define.v"; module DataMem(input wire clk,input…

【原子工具】快速冪 快速乘

題冪算.一切即1 陰陽迭變積微著&#xff0c;疊浪層巒瞬息功 莫道浮生千萬事&#xff0c;元知萬象一歸宗 文章目錄 快速冪原始快速冪&#xff08;O(logn)&#xff09;二分遞歸形式非遞歸形式 模下意義的快速冪&#xff08;O(logn)&#xff09;二分遞歸形式非遞歸形式 快速乘龜速…

文件基礎IO

理解"文件" 1-1 狹義理解 文件在磁盤里磁盤是永久性存儲介質&#xff0c;因此文件在磁盤上的存儲是永久性的磁盤是外設&#xff08;即是輸出設備也是輸入設備&#xff09;磁盤上的文件 本質是對文件的所有操作&#xff0c;都是對外設的輸入和輸出簡稱IO 1-2 廣義理…

Unity 簡易的UI框架

核心內容 UIType.cs namespace MYTOOL.UI {/// <summary>/// UI層級/// </summary>public enum UILayer{/// <summary>/// 主界面層/// </summary>MainUI 0,/// <summary>/// 普通界面層/// </summary>NormalUI 1,/// <summary>/…

VUE2雙向綁定的原理

文章目錄 VUE2雙向綁定的原理1. 什么是雙向綁定2. 雙向綁定的原理2.1 ViewModel的重要作用2.2 雙向綁定的流程 3. 雙向綁定的實現3.1 data響應化處理3.2 Compile編譯3.3 依賴收集 VUE2雙向綁定的原理 1. 什么是雙向綁定 講雙向綁定先講單項綁定&#xff0c;啥叫單項綁定&…

4G核心網的演變與創新:從傳統到虛擬化的跨越

4G核心網 隨著移動通信技術的不斷發展&#xff0c;4G核心網已經經歷了從傳統的硬件密集型架構到現代化、虛擬化網絡架構的重大轉型。這一演變不僅提升了網絡的靈活性和可擴展性&#xff0c;也為未來的5G、物聯網&#xff08;LOT&#xff09;和邊緣計算等技術的發展奠定了基礎。…

云計算——AWS Solutions Architect – Associate(saa)1、什么是云,AWS介紹

什么是云? 什么是云? 云計算(cloud computing)是基于互聯網的相關服務的增加、使用和交付模式&#xff0c;通常涉及通過互聯網來提供動態易護展且經常是虛擬化的資源。云是網絡、互聯網的一種比喻說法。 簡單理解為&#xff1a;云是 共享資源&#xff0c;按需付費&#xff0…

HTML排版標簽、語義化標簽、塊級和行內元素詳解

目錄 前言 一、HTML中的排版標簽 1. 文本相關標簽 1.1 標題標簽 ~ 1.2 段落標簽 1.3 強調和加粗 1.4 換行標簽 1.5 水平線標簽 二、HTML中的語義化標簽 2.1 語義化標簽概述 2.2 常見的語義化標簽 示例&#xff08;核心代碼部分&#xff09;&#xff1a; 三、HTM…

【字節青訓營-7】:初探 Kitex 字節微服務框架(使用ETCD進行服務注冊與發現)

本文目錄 一、Kitex概述二、第一個Kitex應用三、IDL四、服務注冊與發現 一、Kitex概述 長話短說&#xff0c;就是字節跳動內部的 Golang 微服務 RPC 框架&#xff0c;具有高性能、強可擴展的特點&#xff0c;在字節內部已廣泛使用。 如果對微服務性能有要求&#xff0c;又希望…

【數學】矩陣、向量(內含矩陣乘法C++)

目錄 一、前置知識&#xff1a;向量&#xff08;一列或一行的矩陣&#xff09;、矩陣1. 行向量2. 列向量3. 向量其余基本概念4. 矩陣基本概念5. 關于它們的細節 二、運算1. 轉置&#xff08;1&#xff09;定義&#xff08;2&#xff09;性質 2. 矩陣&#xff08;向量&#xff0…

TCP/IP 郵件

TCP/IP 郵件 引言 在互聯網技術飛速發展的今天,電子郵件(Email)已成為人們日常工作和生活中不可或缺的通信工具。TCP/IP協議作為互聯網通信的基礎,為電子郵件的傳輸提供了強大的技術支持。本文將詳細介紹TCP/IP在電子郵件傳輸過程中的作用,以及相關的協議和實現方式。 …

離線安裝Appium Server

1、問題概述? 安裝Appium通常有兩種方式: 第一種:下載exe安裝包,這種是Appium Server GUI安裝方式,缺點是通過命令啟動不方便。 第二種:通過cmd安裝appium server,可以通過命令方式啟動,比較方便。 問題:在沒有外網的情況下,無法通過命令在cmd中安裝appium server…

設計模式六大原則和單例模式

設計模式 目的 實現可重用解決方案&#xff0c;構筑易維護、可擴展的軟件系統。 六大原則 單一職責&#xff1a; 類的職責單一&#xff0c;一個方法做一件事。 開閉原則&#xff1a; 拓展開放&#xff0c;修改關閉。 里氏替換&#xff1a; 父類能出現的地方&#xff0c;子…

淺嘗yolo11全程記錄1-準備環境+官網模型推理(個人備份)

準備工作&#xff08;虛擬環境、導入項目&#xff09; 安裝Anaconda 主要是為了創建和管理虛擬環境&#xff0c;在pycharm里按照項目里的requirments.txt安裝依賴的時候&#xff0c;使用虛擬環境會好很多&#xff08;我記得不用Anaconda也可以直接在pycharm的terminal里頭創建…

5.攻防世界 fileinclude

進入題目頁面如下 提示flag在flag.php ctrlu&#xff0c;查看源碼 給出了一段PHP代碼&#xff0c;進行代碼審計 <?php // 檢查是否開啟了錯誤顯示功能 if( !ini_get(display_errors) ) {// 如果沒有開啟&#xff0c;則將錯誤顯示功能設置為開啟狀態ini_set(display_error…

深入淺出 NRM:加速你的 npm 包管理之旅

文章目錄 前言一、NRM 是什么&#xff1f;二、為什么需要 NRM&#xff1f;三、NRM 的優勢四、NRM 的安裝與使用4.1 安裝 NRM4.2 查看可用的 npm 源4.3 切換 npm 源4.4 測試 npm 源速度4.5 添加自定義 npm 源4.6 刪除 npm 源 五、NRM 的進階使用六、總結 前言 作為一名 JavaScr…

《C#之集訓1-20121019c#基礎》

&#xfeff;&#xfeff; C#是微軟公司發布的一種面向對象的、運行于.NET Framework之上的高級程序設計語言。它是微軟公司研究員Anders Hejlsberg的最新成果。 C#曾經的它在我眼中是很高大上的&#xff0c;一直沒有目睹其風采&#xff0c;現在終于揭開了它神秘的面紗&#xf…

紅包雨項目前端部分

創建項目 pnpm i -g vue/cli vue create red_pakage pnpm i sass sass-locader -D pnpm i --save normalize.css pnpm i --save-dev postcss-px-to-viewportpnpm i vantlatest-v2 -S pnpm i babel-plugin-import -Dhttps://vant.pro/vant/v2/#/zh-CN/<van-button click&…