gpt4 book ai didi

函数返回结构的调用约定

转载 作者:IT王子 更新时间:2023-10-28 23:54:56 25 4
gpt4 key购买 nike

对于以下 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 ;;;;;;;;;;;;;;

最佳答案

Why does the caller in Linux32 do these extra subs?

原因是编译器注入(inject)了一个隐藏指针(named return value optimization),用于按值返回结构。在 SystemV 的 ABI ,第 41 页,在关于“函数返回结构或 union ”的部分中,它说:

The called function must remove this address from the stack before returning.

这就是为什么你得到一个 ret $0x4test_callee5() 的末尾,这是为了符合 ABI。

现在关于 sub $0x4, %esp 的存在在每个 test_callee5() 之后调用站点,它是上述规则的副作用,结合了 C 编译器生成的优化代码。由于本地存储堆栈空间完全由以下方式预留:

3:  sub    $0x38,%esp

不需要压入/弹出隐藏指针,它只是写在预留空间的底部(由 esp 指向),使用 mov %eax,(%esp)在第 9 行和第 17 行。由于堆栈指针没有递减,sub $0x4,%esp是否可以抵消 ret $0x4 的影响, 并保持堆栈指针不变。

在 Win32 上(我猜是使用 MSVC 编译器),没有这样的 ABI 规则,一个简单的 ret使用(如 cdecl 中预期的那样),隐藏指针在第 7 行和第 11 行被压入堆栈。尽管如此,作为优化,这些槽在调用后并未释放,但仅在被调用者退出之前使用 add esp,1Ch 释放。 , 释放隐藏的指针堆栈槽(2 * 0x4 字节)和本地 AStruct结构(0x14 字节)。

Doesn't cdecl define the calling convention for a function returning a structure?!

不幸的是,它不会,它因 C 编译器和操作系统而异

关于函数返回结构的调用约定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4931195/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com