2011-02-08 102 views
11

對於下面的C代碼:調用約定函數返回結構

struct _AStruct { 
    int a; 
    int b; 
    float c; 
    float d; 
    int e; 
}; 

typedef struct _AStruct AStruct; 

AStruct test_callee5(); 
void test_caller5(); 

void test_caller5() { 
    AStruct g = test_callee5(); 
    AStruct h = test_callee5();  
} 

我得到以下拆裝的Win32:

_test_caller5: 
    00000000: lea   eax,[esp-14h] 
    00000004: sub   esp,14h 
    00000007: push  eax 
    00000008: call  _test_callee5 
    0000000D: lea   ecx,[esp+4] 
    00000011: push  ecx 
    00000012: call  _test_callee5 
    00000017: add   esp,1Ch 
    0000001A: ret 

而對於linux32鏡像:

00000000 <test_caller5>: 
    0: push %ebp 
    1: mov %esp,%ebp 
    3: sub $0x38,%esp 
    6: lea 0xffffffec(%ebp),%eax 
    9: mov %eax,(%esp) 
    c: call d <test_caller5+0xd> 
    11: sub $0x4,%esp ;;;;;;;;;; Note this extra sub ;;;;;;;;;;;; 
    14: lea 0xffffffd8(%ebp),%eax 
    17: mov %eax,(%esp) 
    1a: call 1b <test_caller5+0x1b> 
    1f: sub $0x4,%esp ;;;;;;;;;; Note this extra sub ;;;;;;;;;;;; 
    22: leave 
    23: ret 

我試圖瞭解呼叫後呼叫方的不同表現。 爲什麼Linux32中的調用者做這些額外的潛艇?

我會假設兩個目標都遵循cdecl調用約定。 cdecl是否定義了返回結構的函數的調用約定?

編輯:

我添加了被調用者的實現。果然,你可以看到linux32鏡像,被叫彈出它的參數,而在Win32被叫方不:

AStruct test_callee5() 
{ 
    AStruct S={0}; 
    return S; 
} 

的Win32拆解:

test_callee5: 
    00000000: mov   eax,dword ptr [esp+4] 
    00000004: xor   ecx,ecx 
    00000006: mov   dword ptr [eax],0 
    0000000C: mov   dword ptr [eax+4],ecx 
    0000000F: mov   dword ptr [eax+8],ecx 
    00000012: mov   dword ptr [eax+0Ch],ecx 
    00000015: mov   dword ptr [eax+10h],ecx 
    00000018: ret 

linux32鏡像,拆卸:

00000000 <test_callee5>: 
    0: push %ebp 
    1: mov %esp,%ebp 
    3: sub $0x20,%esp 
    6: mov 0x8(%ebp),%edx 
    9: movl $0x0,0xffffffec(%ebp) 
    10: movl $0x0,0xfffffff0(%ebp) 
    17: movl $0x0,0xfffffff4(%ebp) 
    1e: movl $0x0,0xfffffff8(%ebp) 
    25: movl $0x0,0xfffffffc(%ebp) 
    2c: mov 0xffffffec(%ebp),%eax 
    2f: mov %eax,(%edx) 
    31: mov 0xfffffff0(%ebp),%eax 
    34: mov %eax,0x4(%edx) 
    37: mov 0xfffffff4(%ebp),%eax 
    3a: mov %eax,0x8(%edx) 
    3d: mov 0xfffffff8(%ebp),%eax 
    40: mov %eax,0xc(%edx) 
    43: mov 0xfffffffc(%ebp),%eax 
    46: mov %eax,0x10(%edx) 
    49: mov %edx,%eax 
    4b: leave 
    4c: ret $0x4 ;;;;;;;;;;;;;; Note this ;;;;;;;;;;;;;; 
+3

不開始用下劃線標識變化:這些名字是保留給編譯器+ libc實現;因爲這是新語言關鍵字使用的起始標識符(例如`_Bool`,C99和`_Alignas`的`_Complex`,C1x的`_Generic`) – Christoph 2011-02-08 09:47:10

+0

您是否也可以反彙編函數本身?然後,您可能會在Windows中找到該功能並提供額外的說明。如上所述,這沒有標準。 「cdecl」,「stdcall」等等不是C/C++標準的一部分。 – Lundin 2011-02-08 09:57:12

回答

7

爲什麼Linux32的調用者做這些額外的潛艇?

原因在於使用由編譯器注入的隱藏指針(named return value optimization),用於通過值返回結構體。在SystemV的ABI,第41頁,在部分關於「功能返回結構或聯合」,它說:

被調用的函數必須從堆棧中返回之前刪除該地址。

這就是爲什麼你在test_callee5()年底獲得ret $0x4,它是遵守ABI。

現在每個test_callee5()呼叫站點後面存在sub $0x4, %esp,這是上述規則的一個副作用,與C編譯器生成的優化代碼相結合。作爲本地存儲堆棧空間是由完全預保留:

3: sub $0x38,%esp 

沒有必要到PUSH/POP隱藏指針,它只是寫在預保留空間的底部(在由esp指出) ,在第9行和第17行使用mov %eax,(%esp)。由於堆棧指針未遞減,因此sub $0x4,%esp用於抵消ret $0x4的影響,並保持堆棧指針不變。我想,在沒有這樣的ABI規則的情況下,使用了一個簡單的ret(正如在cdecl中所預期的那樣),隱藏的指針在第7行和第11行被壓入堆棧。雖然,那些插槽不會在調用後釋放,作爲優化,但僅在被調用者退出之前,使用add esp,1Ch釋放隱藏的指針堆棧插槽(2 * 0x4字節)和本地結構(0x14字節)。

cdecl不定義函數返回結構的調用約定嗎?!

不幸的是,它不,它與C編譯器和操作系統

0

有沒有單一的「cdecl」調用約定。它由編譯器和操作系統定義。

同時閱讀程序集實際上我並不確定約定實際上是不同的—在這兩種情況下調用者都爲輸出提供緩衝區作爲額外參數。只是gcc選擇了不同的指令(第二個額外的子程序很奇怪,代碼是否優化?)。