【问题标题】:How to return a complex return value?如何返回一个复杂的返回值?
【发布时间】:2010-09-13 16:05:13
【问题描述】:

目前我正在编写一些汇编语言程序。正如一些约定所说,当我想向调用者返回一些值时,比如一个整数,我应该在 EAX 寄存器中返回它。现在我想知道如果我想返回一个浮点数、一个双精度数、一个枚举甚至一个复杂的结构怎么办。如何返回这些类型的值?

我可以考虑在 EAX 中返回一个地址,该地址指向内存中的实际值。但这是标准方式吗?

非常感谢~~~

【问题讨论】:

  • 如果你想知道“标准方式”,你需要指定你的平台。您使用什么硬件、什么操作系统和什么编译器?
  • @StephenCanon 我在发布问题时没有意识到这些是相关的。谢谢。

标签: c++ c assembly x86 calling-convention


【解决方案1】:

如果调用者是您的代码,这完全取决于您。如果调用者不在你的控制之下,你必须要么遵循他们现有的约定,要么一起制定自己的约定。

例如,在 x86 平台上,当 FPU 指令处理浮点运算时,函数的结果作为 FPU 寄存器堆栈的顶部值返回。 (如果您知道,x86 FPU 寄存器被组织成某种“循环堆栈”)。此时它既不是float也不是double,它是一个以内部FPU精度存储的值(可能高于floatdouble),调用者有责任从顶部检索该值FPU 堆栈并将其转换为所需的任何类型。事实上,这就是典型的 FPU 指令的工作方式:它从 FPU 堆栈的顶部获取参数并将结果推回 FPU 堆栈。通过以同样的方式实现您的功能,您实际上可以用您的功能模拟“复杂”的 FPU 指令——这是一种相当自然的方式。

当 SSE 指令处理浮点运算时,您可以选择一些 SSE 寄存器用于相同目的(使用 xmm0,就像使用 EAX 处理整数一样)。

对于复杂的结构(即大于一个寄存器或一对寄存器的结构),调用者通常会将一个指向保留缓冲区的指针传递给函数。该函数会将结果放入缓冲区。换句话说,在底层,函数永远不会真正“返回”大对象,而是在调用者提供的内存缓冲区中构造它们。

当然,您可以使用这种“内存缓冲区”方法返回任何类型的值,但是对于较小的值,即标量类型的值,使用寄存器比使用内存位置更有效。顺便说一句,这也适用于小型结构。

枚举通常只是一些整数类型的概念包装。因此,返回枚举或整数没有区别。

【讨论】:

    【解决方案2】:

    一个双精度值应作为堆栈中的第一项返回。

    这是一个 C++ 代码示例 (x86):

    double sqrt(double n)
    {
        _asm fld n
        _asm fsqrt
    }  
    

    如果您更喜欢手动管理堆栈(节省一些 CPU 周期):

    double inline __declspec (naked) __fastcall sqrt(double n)
    {
        _asm fld qword ptr [esp+4]
        _asm fsqrt
        _asm ret 8
    }
    

    对于复杂类型,你应该传递一个指针,或者返回一个指针。

    【讨论】:

    • 如果您说您还需要指定编译器(带有版本)和操作系统(带有版本),尽管您可能应该为您的编译器指定 ABI 文档。这是因为并非所有 C++ 编译器都这样做。所有 C 编译器都会,因为 C ABI 定义明确。
    • 对。它适用于 Win32、VC++ 2008。但可能适用于所有 VC 版本。
    【解决方案3】:

    当您对调用约定或汇编语言有疑问时,请用高级语言编写一个简单的函数(在单独的文件中)。接下来,让您的编译器生成一个汇编语言列表或让您的调试器显示“交错汇编”。

    清单不仅会告诉您编译器如何实现代码,还会向您展示调用约定。比发布到 S.O. 容易得多。而且通常更快。 ;-)

    【讨论】:

      【解决方案4】:

      C99 有一个复杂的内置数据类型 (_Complex)。因此,如果您有一个符合 C99 的编译器,您可以只编译一些返回复数的函数并将其编译为汇编程序(通常使用 -S 选项)。在那里你可以看到所采用的约定。

      【讨论】:

        【解决方案5】:

        这取决于 ABI。例如,x86 上的 Linux 使用 Intel386 Architecture Processor Supplment, Fourth Edition 中指定的 Sys V ABI。

        函数调用序列部分包含有关如何返回值的信息。简而言之,在这个 API 中:

        • 返回标量或无值的函数使用%eax
        • 返回浮点值的函数使用%st(0)
        • 对于返回structunion 类型的函数,调用者为返回值提供空间并将其地址作为隐藏的第一个参数传递。被调用者在%eax中返回这个地址。

        【讨论】:

          【解决方案6】:

          通常你会使用堆栈

          【讨论】:

          • 使用 C ABI 你使用堆栈。在 C++ 中它是未定义的,因此允许编译器制造商选择更有效的机制(如果他们愿意的话)。
          【解决方案7】:

          如果您打算与 C 或其他高级语言交互,通常您会接受内存缓冲区的地址作为函数的参数,并通过填充该缓冲区来返回您的复数值。如果这只是汇编,那么您可以使用您想要的任何一组寄存器来定义自己的约定,尽管通常只有在您有特定原因(例如性能)时才会这样做。

          【讨论】:

            猜你喜欢
            • 2019-05-20
            • 1970-01-01
            • 1970-01-01
            • 2013-01-21
            • 1970-01-01
            • 1970-01-01
            • 2012-01-23
            • 2013-11-06
            • 2019-11-05
            相关资源
            最近更新 更多