如何返回复杂的返回值?

时间:2010-09-13 16:05:13

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

目前我正在编写一些汇编语言程序。正如一些惯例所说,当我想向调用者返回一些值时,比如一个整数,我应该在EAX寄存器中返回它。现在我想知道如果我想返回一个float,一个double,一个枚举,甚至一个复杂的结构。如何返回这些类型的值?

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

非常感谢~~~

7 个答案:

答案 0 :(得分:10)

如果调用者是您的代码,那完全取决于您。如果呼叫者不在您的控制范围内,您必须遵循现有惯例或共同制定自己的惯例。

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

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

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

当然,您可以使用此“内存缓冲区”方法返回任何类型的值,但是使用较小的值(即标量类型的值),使用寄存器比使用内存位置要高效得多。这也适用于小型结构。

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

答案 1 :(得分:6)

应该返回一个double作为堆栈中的第一项。

这是一个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
}

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

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

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

答案 5 :(得分:0)

通常你会使用堆栈

答案 6 :(得分:0)

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