函數返回值存放在**調用者的棧幀**中,具體位於**被調用函數活動記錄的「返回值域」**,最終通過 CPU 寄存器 (如 x86 的 EAX/RAX, ARM 的 R0) 傳遞給調用者。
當函數被調用時,系統在運行時棧上為其分配一塊連續內存區域 —— **活動記錄**,結構如下 (從低地址到高地址):**
+-------------------------+ <-- 低地址 (棧底方向)
| 實參區 | 主調函數壓入的參數 (右至左)
+-------------------------+
| 返回地址 | CALL 指令自動壓入
+-------------------------+
| 舊幀指針 (EBP/RBP) | 構建鏈表,便於回溯
+-------------------------+
| 局部變量區 | 函數內部定義的變量、數組
+-------------------------+
| 臨時變量/表達式計算區 | 編譯器生成的中間結果
+-------------------------+
| 返回值域 ★★★ | **被調函數寫入返回值的位置**
+-------------------------+ <-- 高地址 (棧頂方向, ESP/RSP 指向此處)
CALL func (隱含壓入返回地址,跳轉)。PUSH EBP; MOV EBP, ESP; SUB ESP, local_size 建立新幀。EAX 寄存器 (或浮點返回值寫 ST0),或若返回值過大 (如結構體),則寫入調用者預分配的內存區 (由隱藏參數指針指向)。MOV ESP, EBP; POP EBP; RET 恢復棧指針,彈出返回地址跳轉回調用者。ADD ESP, param_bytes 清理參數棧空間,**從 EAX 讀取返回值**繼續執行。func(&ret_val, ...)),被調用者寫入該地址。此時 EAX 通常返回該指針值。
// test.c
int foo() { return 42; }
int main() { int a = foo(); return a; }
// GCC 編譯: gcc -S -O0 test.c -o test.s
// 關鍵片段:
foo:
pushl %ebp
movl %esp, %ebp
movl $42, %eax # 返回值直接寫入 EAX
popl %ebp
ret
main:
pushl %ebp
movl %esp, %ebp
call foo
movl %eax, -4(%ebp) # 從 EAX 取值存入局部變量 a
movl -4(%ebp), %eax
popl %ebp
ret
| 誤區 | 事實 |
|---|---|
| 返回值存在「全局內存」或「堆」上 | 絕大多數標量返回值在**棧/寄存器**,生命周期隨調用結束而釋放 |
return 指令會「拷貝」數據到調用者 |
標量通過**寄存器傳遞**,零拷貝;大對象由編譯器安排內存位置 |
| 局部變量地址可作返回值 | **極度危險**!局部變量在函數返回時銷毀,返回其地址導致懸空指針 |
void func(OutType *out)) 或 C++ 移動語義/std::optional。return local_obj; 通常零開銷。info registers eax (或 rax) 可直接查看剛返回的整數值,無需進入調用者幀。C 語言返回值機制本質是**約定俗成的棧幀布局與寄存器使用協議**。理解活動記錄結構、調用約定、編譯器優化 (RVO),是寫出高性能、無 UB 代碼、進行底層調試的核心內功。