4

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

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

非常感谢~~~

4

7 回答 7

9

如果调用者是您的代码,这完全取决于您。如果调用者不受您的控制,您必须遵循他们现有的约定或一起开发自己的约定。

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

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

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

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

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

于 2010-09-13T16:22:14.583 回答
5

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

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

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

于 2010-09-13T16:43:09.777 回答
2

When you have questions about calling conventions or assembly language, write a simple function in high level language (in a separate file). Next, have your compiler generate an assembly language listing or have your debugger display "interleaved assembly".

Not only will the listing tell you how the compiler implements code, but also show you the calling conventions. A lot easier than posting to S.O. and usually faster. ;-)

于 2010-09-13T20:50:50.373 回答
1

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

于 2010-09-13T18:55:38.890 回答
1

这取决于 ABI。例如,x86 上的 Linux 使用Intel386 架构处理器补充第四版中指定的 Sys V ABI 。

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

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

通常你会使用堆栈

于 2010-09-13T16:06:51.433 回答
0

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

于 2010-09-13T16:16:40.570 回答