首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 函數呼叫堆疊幀深度解析:參數傳遞、現場保護與恢復

     編輯:關於C++

函數呼叫核心流程

函數呼叫的完整過程包含四個關鍵階段:參數入棧 → 函數跳轉 → 保護現場 → 恢復現場。編譯器利用三個專用暫存器管理執行流程:

  • EIP (Instruction Pointer):指令指標,指向下一條將執行指令的位址。
  • EBP (Base Pointer):基址指標,指向當前棧幀底部(棧底),作為區域變數與參數存取的基準。
  • ESP (Stack Pointer):棧指標,指向當前棧頂,隨資料壓入/彈出動態變化。

實戰範例逐步拆解

假設呼叫 g_func(1, 2, 3),對應組合語言指令流程如下:

階段 1:參數入棧(從右向左)

push 3    ; 參數 3
push 2    ; 參數 2
push 1    ; 參數 1

此時 ESP 指向棧頂,記憶體依序儲存 1, 2, 3。呼叫慣例採用右向左壓棧,便於支援可變參數函數(如 printf)。

階段 2:函數跳轉

call 0x00401005   ; 呼叫跳轉指令
; 0x00401005 處為 jmp g_func 實際入口 0x00401030

call 指令隱含執行:將下一條指令位址(返回位址)壓入棧,再跳轉。此例中返回位址為 0x00401093(add 指令)。

階段 3:保護現場(建立新棧幀)

push ebp          ; 保存呼叫者 EBP
mov  ebp, esp     ; 設定新棧幀基址
sub  esp, 0x48    ; 配置區域變數空間(含 0x40 位元組間隔 + 2 個 int 區域變數 8 位元組)
push ebx          ; 保存被呼叫者需保留的暫存器
push esi
push edi

關鍵細節:

  • sub esp, 0x48 中的 0x40 (64 bytes) 為固定間隔區,隔離呼叫者與被呼叫者棧區;剩餘 8 bytes 供區域變數 x, y 使用。
  • ebx, esi, edi 為被呼叫者保存暫存器,呼叫慣例要求被呼叫者保持不變。

此時棧記憶體佈局(由高位址到低位址)

[呼叫者區域變數...]
[參數 1][參數 2][參數 3]   ← 呼叫者壓入
[返回位址 0x00401093]       ← call 隱含壓入
[舊 EBP]                   ← push ebp
[區域變數 x][區域變數 y]    ← sub esp 配置
[間隔區 64 bytes]
[舊 EBX][舊 ESI][舊 EDI]    ← 被呼叫者保存
          ↑
         ESP (當前棧頂)
         EBP (當前棧底)

現場恢復與返回

pop  edi          ; 還原暫存器
pop  esi
pop  ebx
mov  esp, ebp     ; 釋放區域變數空間
pop  ebp          ; 還原呼叫者 EBP
ret               ; 彈出返回位址跳轉,等價於 pop eip

ret 指令從棧頂取出返回位址載入 EIP,控制權回到呼叫者。呼叫者隨後執行 add esp, 0xC 清理參數空間(__cdecl 慣例由呼叫者清棧)。

關鍵暫存器角色總結

暫存器角色呼叫慣例責任
EAX, ECX, EDX易失性暫存器呼叫者保存
EBX, ESI, EDI非易失性暫存器被呼叫者保存
EBP棧幀基址被呼叫者保存/建立
ESP棧頂指標硬體/指令自動管理
EIP指令指標call/ret/jmp 控制

圖示說明

函數呼叫前棧狀態 參數入棧後記憶體 呼叫指令序列 跳轉指令 返回位址入棧 棧幀建立後佈局 完整棧記憶體圖 恢復現場步驟1 恢復現場步驟2 恢復現場步驟3

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved