一位同事在用C++結構注意到一個奇怪的行爲之後,用這個問題戳穿了我。爲C++結構定義顯式析構函數如何影響調用約定?
就拿這個簡單的代碼:
struct S {
int i;
#ifdef TEST
~S() {}
#endif
};
void foo (S s) {
(void)s;
}
int main() {
foo(S());
return 0;
}
我已經生成的彙編代碼,一旦沒有明確的析構函數:
g++-4.7.2 destructor.cc -S -O0 -o destructor_no.s
以後包括它:
g++-4.7.2 destructor.cc -DTEST -S -O0 -o destructor_yes.s
這是代碼[0123]爲main
,代碼爲destructor_no.s
:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
movl %eax, %edi
call _Z3foo1S // call to foo()
movl $0, %eax
popq %rbp
ret
雖然,相反,如果析構函數被明確定義:
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $0, -16(%rbp)
leaq -16(%rbp), %rax
movq %rax, %rdi
call _Z3foo1S // call to foo()
leaq -16(%rbp), %rax
movq %rax, %rdi
call _ZN1SD1Ev // call to S::~S()
movl $0, %eax
leave
ret
現在,我的裝配知識是有點生疏,但我認爲:
在第一種情況是,結構是「按值」傳遞的。也就是說,它的存儲器內容被複制到
%edi
寄存器中,如果我沒有弄錯的話,它是第一個寄存器用於在x86-64
ABI中傳遞參數。在第二種情況下,取而代之的是,結構被分配在堆棧上,但
foo()
函數用%rdi
中的指針調用。
爲什麼會有這樣的差異?
注:
出現同樣的問題,如果使用
gcc-4.6.3
,或者clang 3.1
證實。當然,如果已啓用優化,則在任何情況下都會完全優化對函數
foo()
的調用。向
struct
添加更多變量(如果未明確提供析構函數)時,會出現一個有趣的模式。
最多4 int
秒(= 16個字節)被通過所述參數寄存器傳遞:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $0, -16(%rbp)
movl $0, -12(%rbp)
movl $0, -8(%rbp)
movl $0, -4(%rbp)
movq -16(%rbp), %rdx
movq -8(%rbp), %rax
movq %rdx, %rdi
movq %rax, %rsi
call _Z3foo1S
但只要我添加第五int
的struct,參數的功能,仍然是「由值」通過,現在是在堆棧上:
[1]我已刪除了部分行,我認爲是不必要的爲這個問題的目的。
如果用'-O3'編譯,兩者有什麼不同? – Praetorian
當你添加一個析構函數時,對象必須超過對'foo'的調用才能被銷燬。至於第二個觀察,似乎是一個有意識的決定,沿着「嘗試將這些參數放入寄存器中,然後放回棧中」的方式來進行。 – DCoder
@Prætorian使用'-O3',在兩種情況下都可以優化整個呼叫。 –