typedef struct { bool sex; } person;
person main() {
person p = {0};
return p; // 編譯通过,運行 SegFault
}
EAX (32位)main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp ; 分配棧幀
movl 8(%ebp), %eax ; 【關鍵】取隱藏參數:返回值地址
movb $0, -1(%ebp) ; p.sex = 0
movzbl -1(%ebp), %edx
movb %dl, (%eax) ; 寫入返回值地址
leave
ret $4 ; 清理隱藏參數
8(%ebp) 是調用者壓棧的返回值緩衝區地址。但 main 是進程入口,由啟動代碼 (crt0) 調用,不會傳遞這個隱藏參數。啟動代碼棧佈局:
[argc] [argv] [envp] ← main 見到的參數
無隱藏返回值地址參數!
因此 %eax 讀到的是垃圾值 (如 0x1),隨後 movb %dl, (%eax) 寫入非法地址 → 段錯誤。
int main(void) { /* ... */ }
int main(int argc, char *argv[]) { /* ... */ }
// 等效實現定義簽名,返回 int
main 必須返回 int,否則行為未定義。
// 1. 輸出參數模式 (推薦)
bool create_person(person *out) {
if (!out) return false;
out->sex = 0;
return true;
}
// 2. 動態分配 (需文檔說明所有權)
person* create_person() {
person *p = malloc(sizeof(person));
if (p) p->sex = 0;
return p;
}
// 3. 全局/靜態緩衝區 (非線程安全)
person* get_default_person() {
static person p = {0};
return &p;
}
非 main 函數返回結構體 完全合法且高效 (C99 保證)。編譯器會自動處理隱藏參數:
person foo() { return (person){1}; } // 正常工作
// 調用方反匯編
sub $16, %esp ; 分配返回值緩衝區
lea -16(%ebp), %eax ; 取地址
push %eax ; 隱藏第一參數
call foo
add $4, %esp ; 清理參數
; 緩衝區內已有返回值
// C++17 拷贝消除 (NRVO/RVO) + 移動語義
Person create() { return Person{0}; } // 零開銷返回
// 結構體返回無任何性能懸念
main 是特殊的進程入口,其調用約定由運行時啟動代碼決定,**不支持隱藏返回值參數**。任何嘗試讓 main 返回非 int 類型的行為都是未定義行為。記住鐵律:main 只能返回 int,結構體返回請用輸出參數或動態分配。