对于以下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 ;;;;;;;;;;;;;;
答案 0 :(得分:9)
为什么Linux32中的调用者会执行这些额外的子服务?
原因是使用由编译器注入的隐藏指针(set_index
)来按值返回结构。在SystemV的named return value optimization,第41页,在&#34;功能返回结构或联盟&#34;部分中,它说:
被调用的函数必须在返回之前从堆栈中删除此地址。
这就是为什么你在sub $0x4, %esp
结尾处获得test_callee5()
,这是为了遵守ABI。
现在关于每个3: sub $0x38,%esp
呼叫站点之后esp
的存在,它是上述规则的副作用,并结合了C编译器生成的优化代码。由于本地存储堆栈空间完全由以下预先保留:
mov %eax,(%esp)
不需要按下/弹出隐藏指针,它只是在第9行和第17行使用sub $0x4,%esp
写在预保留空间的底部(由ret $0x4
指向)。由于堆栈指针没有递减,ret
用于否定add esp,1Ch
的效果,并保持堆栈指针不变。
在Win32上(我猜是使用MSVC编译器),没有这样的ABI规则,使用简单的AStruct
(如cdecl中预期的那样),隐藏指针在第7行和第11行被压入堆栈。虽然,这些插槽在调用之后不会被释放,作为优化,但只有在被调用者退出之前,使用{{1}},释放隐藏的指针堆栈槽(2 * 0x4字节)和本地{{1}}结构(0x14字节)。
没有cdecl定义返回结构的函数的调用约定吗?!
不幸的是,它没有,它随C编译器和操作系统而变化
答案 1 :(得分:0)
没有单一的“cdecl”调用约定。它由编译器和操作系统定义。
同样读取程序集我实际上并不确定约定实际上是不同的 - 在这两种情况下,调用者都为输出提供缓冲区作为额外参数。只是gcc选择了不同的指令(第二个额外的子指令是奇怪的;是否优化了代码?)。