目前我正在编写一些汇编语言程序。正如一些惯例所说,当我想向调用者返回一些值时,比如一个整数,我应该在EAX寄存器中返回它。现在我想知道如果我想返回一个float,一个double,一个枚举,甚至一个复杂的结构。如何返回这些类型的值?
我可以想到在EAX中返回一个指向内存中实际值的地址。但这是标准方式吗?
非常感谢~~~
答案 0 :(得分:10)
如果调用者是您的代码,那完全取决于您。如果呼叫者不在您的控制范围内,您必须遵循现有惯例或共同制定自己的惯例。
例如,在x86平台上,当FPU指令处理浮点运算时,函数的结果将作为FPU寄存器堆栈的最高值返回。 (如果你知道,x86 FPU寄存器被组织成各种各样的“循环堆栈”)。那时它既不是float
也不是double
,它是一个以内部FPU精度存储的值(可能高于float
或double
)并且它是调用者的从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)
; struct
或union
类型的函数,调用者为返回值提供空间,并将其地址作为隐藏的第一个参数传递。被调用者在%eax
中返回此地址。答案 5 :(得分:0)
通常你会使用堆栈
答案 6 :(得分:0)
如果您计划与C或其他更高级语言进行交互,通常您会接受内存缓冲区的地址作为函数的参数,并通过填充该缓冲区来返回复杂值。如果这只是汇编,那么你可以使用你想要的任何寄存器来定义你自己的约定,尽管通常你只有在你有特定原因(例如,性能)的情况下这样做。