2011-02-08 110 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选择了不同的指令(第二个额外的子程序很奇怪,代码是否优化?)。