當函數回傳 struct/class 物件時,編譯器在背後做了什麼?本文通過反組譯碼逐行解析,揭示 回傳值優化(RVO/NRVO)之前的經典實作:呼叫端預留空間、傳遞隱藏指標、被呼叫端構造臨時物件、呼叫端再拷貝/移動的完整流程。
struct Big { char buf[100]; int i; long d; } B, B2;
Big bigfun(Big b) { b.i = 100; return b; }
int main() { B2 = bigfun(B); }
| 一般函數 | 回傳結構體的函數 |
|---|---|
| 局部變數 | 局部變數 |
| EBP | EBP |
| EIP (回傳地址) | EIP |
| 參數 | 回傳值臨時物件地址 (隱藏參數) |
| 參數 |
因寄存器太小無法裝下大型結構體,呼叫端在堆疊預留空間,將其首地址作為隱藏第一參數傳給被呼叫函數(EBP+8 位置)。
sub esp, 118h 預留空間 → 低址放 bigfun 回傳臨時物件,高址放 B2.operator= 參數。B 複製到參數區(rep movs)→ lea eax, [ebp-0D8h] 取臨時物件地址 → push eax 傳隱藏參數 → call bigfun。b.i=100 → rep movs 將 b 複製到隱藏參數指向的臨時物件 → mov eax, [ebp+8] 將臨時物件地址回傳於 EAX。add esp, 70h 清理參數 → rep movs 臨時物件 → B2.operator= 參數 → 再 rep movs 到 B2 實際位置。sub esp, 118h 顯示 main 預留了兩個 Big 大小的空間。std::optional<Big> 或值型別,依賴 RVO/NRVO(C++17 強制保證)與移動語義。void bigfun(Big& out, const Big& in) 明確控制記憶體佈局。透過反組譯碼看清 return 的真面目,能讓你在優化熱點路徑、排查物件生命週期 Bug 時,擁有「看穿編譯器」的 X 光眼。