当前位置: 首页 > 面试题库 >

函数返回结构的调用约定

章涵容
2023-03-14
问题内容

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

问题答案:

为什么Linux32中的调用程序会执行这些额外的操作?

原因是使用由编译器注入的隐藏指针(称为返回值优化),用于按值返回结构。在SystemV的ABI,第41页的“函数返回结构或联合”部分中,它表示:

被调用的函数必须在返回之前从堆栈中删除该地址。

这就是为什么您ret $0x4在末尾获得a的原因test_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字节)。

cdecl不会为返回结构的函数定义调用约定吗?

不幸的是,事实并非如此,它随C编译器和操作系统的不同而不同。



 类似资料:
  • 问题内容: 我正在使用Postgresql 8.3,并具有以下简单功能,该功能会将a返回 给客户端 现在,我可以使用以下SQL命令来调用此函数并操纵返回的游标,但是游标名称是由PostgreSQL自动生成的 此外,如38.7.3.5中所述,显式地将游标名称声明为函数的输入参数 。返回游标。我可以声明自己的游标名称并使用此游标名称来操纵返回的游标,而不是为我自动生成的Postgresql吗?如果不是

  • 我找到了一些很好的SO链接(如何从异步回调函数返回值?以及从node.js中的回调函数返回值等),但它们并不是不能为我的问题提供解决方案。 我的问题:能够得到异步调用的结果,但我如何使用这个结果返回我的函数? 这里获取callBackResponse的值为true或false,并希望将该值用作:

  • 问题内容: 这个问题已经在这里有了答案 : 如何从异步调用返回响应? (39个答案) 7年前关闭。 我想创建一个JavaScript函数,该函数返回jQuery AJAX调用的值。我想要这样的东西。 我知道我可以通过将async设置为false来做到这一点,但我宁愿不这样做。 问题答案: 使用jQuery 1.5,您可以使用全新的功能,正是为此目的而设计的。 资源

  • 我们目前使用Mockito+PowerMock作为我们的主要模仿框架,并且在开始将一些代码转移到Java8时遇到了一些问题。因此,我们决定将jMockit作为一种替代方案进行评估。我对嘲笑概念有相当好的理解,但我承认我对jMockit的经验非常有限。 然而,我在测试一些在我看来应该是非常基本的东西时遇到了问题:被测试的类在其构造函数中使用New创建其他类的实例。我想让这个新调用返回一个模拟实例。

  • 问题内容: 以下是gcc 4.4.4下的简单代码段错误 将最后一行更改为 工作良好。使用编译时,这两个版本均可使用。我是在简单地调用未定义的行为,还是在标准中进行了某些更改,从而使代码可以在C99下工作?为什么在C89下崩溃? 问题答案: 我相信C89 / C90和C99中的行为均未定义。 是数组类型的表达式,特别是。 C99 6.3.2.1p3说: 除非它是 sizeof 运算符或一元 & 运算

  • 问题内容: 我正在尝试获取返回值,但始终无法定义。 我不确定如何获取任何返回值并在其他地方使用它?我需要使用此返回值来与其他函数进行验证,但是它似乎在范围之内。 这是我的代码: 问题答案: 移动以下行: 进入回调: 如果希望返回某些内容,则还需要向其添加回调,以便可以使用它的返回值。例如,如果您希望这样做: 这将无法正常工作,因为内容的最终值将被异步检索。相反,您需要这样做: 您不能异步返回数据。