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

C 函數調用底層原理:x86 棧幀構建、參數傳遞、返回值與調用慣例完整圖解

     編輯:關於C語言

調用慣例核心規則

以 32 位 x86 cdecl 為例:

  • 參數從右向左壓棧
  • 調用者負責清理棧
  • 返回值放在 EAX (64 位用 EDX:EAX,浮點用 ST0)
  • 被調用者保存:EBX, ESI, EDI, EBP, ESP (非易失性);調用者保存:EAX, ECX, EDX (易失性)

完整調用流程 (以 fun(p0, p1, p2) 為例)

階段 1:調用者壓參數

; 假設參數為立即數
movl $p2, 8(%esp)
movl $p1, 4(%esp)
movl $p0, (%esp)
; 棧頂 (ESP) 指向 p0

階段 2:CALL 指令

call fun
; 硬體自動:push EIP (返回位址) ; ESP -= 4
; 跳轉到 fun 入口

階段 3:被調用者構建棧幀

fun:
    pushl %ebp          ; 保存調用者 EBP
    movl  %esp, %ebp    ; 建立新基址
; 此時棧布局 (高位址→低位址):
; [舊 EBP] ← EBP
; [返回位址]
; [p0]     ← 8(%EBP)
; [p1]     ← 12(%EBP)
; [p2]     ← 16(%EBP)

階段 4:分配區域變數空間

; 方式 1:手動恢復
addl $24, %esp        ; 釋放區域變數空間
popl %ebp             ; 恢復舊 EBP
ret                   ; 彈出返回位址跳轉

; 方式 2:leave 指令 (等價於 movl %ebp,%esp; popl %ebp)
leave
ret

階段 7:調用者清理參數

#include <stdio.h>
#include <stdlib.h>

void fun(int a, int b) {
    int local = 12;
    unsigned int ebp_val, esp_val;
    long ret_addr;
    int p0, p1;

    // 讀取 EBP, ESP
    asm volatile ("movl %%ebp, %%eax\n\t"
                  "movl %%esp, %%ebx\n\t"
                  : "=a"(ebp_val), "=b"(esp_val));
    printf("EBP=%p, ESP=%p, 局部變數空間=%d\n", ebp_val, esp_val, ebp_val - esp_val);

    // 讀取參數與返回位址
    asm volatile ("movl 8(%%ebp), %%eax\n\t"   // p0
                  "movl 12(%%ebp), %%ebx\n\t"  // p1
                  "movl 4(%%ebp), %%ecx\n\t"   // 返回位址
                  : "=a"(p0), "=b"(p1), "=c"(ret_addr));
    printf("p0=%d, p1=%d, 返回位址=%p\n", p0, p1, ret_addr);
}

int main() { fun(1, 2); return 0; }

三大核心洞察

  1. 返回區域變數指標是災難:leave/popl %ebp 瞬間棧幀消失,區域變數內存失效
  2. 區域變數空間編譯期確定:subl $N, %esp 的 N 由符號表決定,非動態
  3. 參數壓棧順序倒置:支援可變參數 (如 printf)——首個參數固定偏移,可解析格式串確定後續參數

手寫彙編調用 C 函數清單

  • 遵守調用慣例:保存非易失性寄存器、對齊棧 (16 字節 for SSE)
  • 正確壓參數、call、清理棧
  • 處理返回值 (EAX)
  • C++ 名稱修飾:用 extern "C" 禁用
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved