返回值看似簡單,實則暗藏玄機。以 Linux x86 為例,程式退出碼存於 eax,Shell 下 echo $? 即可查看。C 標準要求 main 返回 int,恰好與 32 位 eax 寬度一致。若用 void main(),編譯器可能報錯/警告,但若編譯通過,eax 殘留值即成返回碼——這解释了為何呼叫返回非零函數後 main 不顯式返回也能「帶回」該值。
| 類型 | 大小 | 返回寄存器/方式 | 備註 |
|---|---|---|---|
char | 1 B | al (低 8 位) | 符號擴展至 eax (movsbl) |
short | 2 B | ax (低 16 位) | 同上 |
int/long/指標 | 4 B | eax | 標準整數/指標返回 |
long long (64 位) | 8 B | edx:eax | edx 高 32 位,eax 低 32 位 |
float/double | 4/8 B | FPU 堆疊頂 ST(0) | 即使運算結果在 eax,仍需 fld 壓入 FPU |
struct | 任意 | 隱藏參數:呼叫者分配空間,傳指標給被呼叫者 | 關鍵機制,詳見下文 |
GCC (System V ABI) 規定:呼叫者在堆疊壓入返回值緩衝區地址作為第一個隱藏參數,被呼叫者寫入該地址並將地址放回 eax 返回。即使結構體只有 1 byte 也同理。
struct xxx { char a; };
struct xxx f() { struct xxx x; x.a = '9'; return x; }
int main() { struct xxx y = f(); return 0; }
關鍵組合語言片段:
f:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %edx ; 取出隱藏參數 (返回地址) → edx
movb $57, -1(%ebp) ; '9' → 區域變數
movzbl -1(%ebp), %eax ; 擴展至 eax
movb %al, (%edx) ; 寫入呼叫者緩衝區
movl %edx, %eax ; 返回緩衝區地址
leave
ret $4 ; 清理隱藏參數
main 調用前:leal -21(%ebp), %eax; movl %eax, (%esp) — 將區域變數 y 地址壓棧作為隱藏參數。
struct xxx main() 會段錯?main 入口由啟動程式碼呼叫,堆疊佈局非標準函數呼叫慣例。main 以為 8(%ebp) 有隱藏參數地址,實則為環境指標/參數計數等,寫入隨機地址導致段錯誤。
GCC 支援 a = ({ int b=2; int c=3; c-b; }) → a=1。規則:
return。struct 不支援。eax,按目標類型截取 (al/ax/eax/符號擴展/轉浮點)。實際應用:Linux 核心 container_of 宏即利用此特性實現型別安全的容器指標計算。
本文出自「牛哥的博客」部落格,轉載請保留出處:原文連結