2012-10-06 51 views
3

一位同事在用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 

現在,我的裝配知識是有點生疏,但我認爲:

  1. 在第一種情況是,結構是「按值」傳遞的。也就是說,它的存儲器內容被複制到%edi寄存器中,如果我沒有弄錯的話,它是第一個寄存器用於在x86-64 ABI中傳遞參數。

  2. 在第二種情況下,取而代之的是,結構被分配在堆棧上,但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,參數的功能,仍然是「由值」通過,現在是在堆棧上:

​​3210

[1]我已刪除了部分行,我認爲是不必要的爲這個問題的目的。

+0

如果用'-O3'編譯,兩者有什麼不同? – Praetorian

+0

當你添加一個析構函數時,對象必須超過對'foo'的調用才能被銷燬。至於第二個觀察,似乎是一個有意識的決定,沿着「嘗試將這些參數放入寄存器中,然後放回棧中」的方式來進行。 – DCoder

+0

@Prætorian使用'-O3',在兩種情況下都可以優化整個呼叫。 –

回答

3

在C++中,如果你定義了一個析構函數,那麼你的結構不再是POD類型。沒有析構函數的變體的對象的行爲就像一個C結構變量(因此它只是通過值傳遞),而具有用戶定義的變體的行爲就像一個C++對象。

+0

在你的回答中提到了'C++ 03';這是否意味着「C++ 11」標準的情況有所不同? –

+1

http://en.wikipedia.org/wiki/C%2B%2B11#Modification_to_the_definition_of_plain_old_data – filmor

+2

在這種特殊的情況下,沒有什麼區別,就像C++ 11一樣,通過定義一個不重要的類一個析構函數。 – filmor

相關問題