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

C語言函數調用約定深度探秘:參數壓棧順序與返回值機制

     編輯:關於C語言

C語言函數調用約定深度探秘:參數壓棧順序與返回值機制

「參數從右向左壓棧,返回值放在 EAX」——這是 x86 C 調用約定(cdecl)的教科書定論。但為什麼內嵌匯編讀取 EAX 卻得到 0?本文通過反匯編一步步還原真相。

實驗代碼

#include <stdio.h>

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

int main() {
    int eax = 0, z = 0, x = 6, y = 5;
    z = add(x, y);
    __asm__("movl %%eax, %0" : "=r"(eax) : "m"(x));
    printf("z = %d, eax = %d\n", z, eax);
    return 0;
}

輸出:z = 11, eax = 0 —— 明明 add 返回 11,為何 eax 是 0?

反匯編還原現場

  1. main 調用 add 前:push 5; push 6; call add —— 確認從右向左壓棧。
  2. add 內部:mov eax, [ebp+8]; add eax, [ebp+12] —— 結果確實存在 EAX 中。
  3. call 返回後:mov [z], eax —— 返回值正確賦給 z。
  4. 內嵌匯編處:mov eax, [ebp-4] ; 這裡的 eax 是局部變量 eax 的地址!

真相大白

GCC 內嵌匯編的 "=r"(eax) 輸出約束,會將輸出操作數綁定到通用寄存器(通常是 EAX)。編譯器生成代碼:

movl %eax, -4(%ebp)   ; 先把局部變量 eax 的值(0)存入 EAX
; ... 你的 movl %%eax, %0 其實是在讀取這個剛被覆蓋的 EAX

所以:內嵌匯編的輸出約束會覆蓋函數返回殘留在 EAX 中的值!

正確讀取返回值的方法

  1. 直接用 C 變量接收:z = add(x,y); —— 最簡單、最可移植。
  2. 若必須用內嵌匯編,在 call 指令緊接著讀取 EAX,不要插入任何 C 語句:
__asm__(
    "call add\n\t"
    "movl %%eax, %0"
    : "=r"(eax)
    : "m"(x), "m"(y)
);

總結

  • cdecl 約定:參數右→左壓棧,返回值在 EAX,調用者清棧。
  • 內嵌匯編輸出約束會重用 EAX,導致函數返回值被覆蓋。
  • 調試時查看反匯編,比單步執行更直觀。
  • 除非極端優化或教學演示,不要用內嵌匯編讀取返回值,C 語言賦值語句已由編譯器正確處理。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved