简体   繁体   English

如何返回复杂的返回值?

[英]How to return a complex return value?

Currently I am writing some assembly language procedures. 目前我正在编写一些汇编语言程序。 As some convention says, when I want to return some value to the caller, say an integer, I should return it in the EAX register. 正如一些惯例所说,当我想向调用者返回一些值时,比如一个整数,我应该在EAX寄存器中返回它。 Now I am wondering what if I want to return a float, a double, an enum, or even a complex struct. 现在我想知道如果我想返回一个float,一个double,一个枚举,甚至一个复杂的结构。 How to return these type of values? 如何返回这些类型的值?

I can think of returning an address in the EAX which points to the real value in memory. 我可以想到在EAX中返回一个指向内存中真实值的地址。 But is it the standard way? 但这是标准方式吗?

Many thanks~~~ 非常感谢~~~

It is all up to you, if the caller is your code. 如果调用者是您的代码,这完全取决于您。 If the caller is not under your control, you have to either follow their existing convention or develop your own convention together. 如果呼叫者不在您的控制之下,您必须遵循现有惯例或一起制定自己的惯例。

For example, on x86 platform when floating-point arithmetic is processed by FPU instructions, the result of a function is returned as the top value on the FPU register stack. 例如,在x86平台上,当FPU指令处理浮点运算时,函数的结果将作为FPU寄存器堆栈的最高值返回。 (If you know, x86 FPU registers are organized into a "circular stack" of sorts). (如果你知道,x86 FPU寄存器被组织成各种各样的“循环堆栈”)。 At that moment it is neither float nor double , it is a value stored with internal FPU precision (which could be higher than float or double ) and it is the caller's responsibility to retrieve that value from the top of FPU stack and convert it to whatever type it desires. 那时它既不是float也不是double ,它是一个以内部FPU精度存储的值(可能高于floatdouble ),并且调用者有责任从FPU堆栈的顶部检索该值并将其转换为任何值打字它的欲望。 In fact, that is how a typical FPU instruction works: it takes its arguments from the top of FPU stack and pushes the result back onto FPU stack. 实际上,这就是典型的FPU指令的工作原理:它从FPU堆栈的顶部获取其参数,并将结果推回到FPU堆栈。 By implementing your function in the same way you essentially emulate a "complex" FPU instruction with your function - a rather natural way to do it. 通过以相同的方式实现您的功能,您基本上可以使用您的函数模拟“复杂”FPU指令 - 这是一种相当自然的方式。

When floating-point arithmetic is processed by SSE instructions, you can choose some SSE register for the same purpose (use xmm0 just like you use EAX for integers). 当SSE指令处理浮点运算时,您可以选择一些SSE寄存器用于相同的目的(使用xmm0就像使用EAX作为整数一样)。

For complex structures (ie ones that are larger than a register or a pair of registers), the caller would normally pass a pointer to a reserved buffer to the function. 对于复杂结构(即大于寄存器或寄存器对的结构),调用者通常会将指针传递给函数的保留缓冲区。 And the function would put the result into the buffer. 函数会将结果放入缓冲区。 In other words, under the hood, functions never really "return" large objects, but rather construct them in a caller-provided memory buffer. 换句话说,在引擎盖下,函数永远不会真正“返回”大对象,而是在调用者提供的内存缓冲区中构造它们。

Of course, you can use this "memory buffer" method for returning values of any type, but with smaller values, ie values of scalar type, it is much more efficient to use registers than a memory location. 当然,您可以使用此“内存缓冲区”方法返回任何类型的值,但是使用较小的值(即标量类型的值),使用寄存器比使用内存位置要高效得多。 This applies, BTW, to small structures as well. 这也适用于小型结构。

Enums are usually just a conceptual wrapper over some integer type. 枚举通常只是某种整数类型的概念包装。 So, there's no difference between returning a enum or an integer. 因此,返回枚举或整数之间没有区别。

A double should be returned as the 1st item in the stack. 应该返回double作为堆栈中的第1项。

Here is a C++ code example (x86): 这是一个C ++代码示例(x86):

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

If you prefer to manage the stack manually (saving some CPU cycles): 如果您更喜欢手动管理堆栈(节省一些CPU周期):

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

For complex types, you should pass a pointer, or return a pointer. 对于复杂类型,您应该传递指针,或返回指针。

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". 接下来,让编译器生成汇编语言列表或让调试器显示“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 SO and usually faster. 比发布到SO更容易,通常更快。 ;-) ;-)

C99 has a complex builtin data type ( _Complex ). C99具有复杂的内置数据类型( _Complex )。 So if you have a C99 compliant compiler, you could just compile some function that returns a complex and compile this to assembler (usually with a -S option). 因此,如果您有一个符合C99的编译器,您可以编译一些返回复合体的函数并将其编译为汇编程序(通常使用-S选项)。 There you can see the convention that is taken. 在那里你可以看到所采取的约定。

It depends on the ABI. 这取决于ABI。 For example, Linux on x86 uses the Sys V ABI, specified in the Intel386 Architecture Processor Supplment, Fourth Edition . 例如,x86上的Linux使用在Intel386架构处理器补充版第四版中指定的Sys V ABI。

The section Function Calling Sequence section has the information on how values are to be returned. 函数调用序列”部分包含有关如何返回值的信息。 Briefly, in this API: 简而言之,在此API中:

  • Functions returning scalars or no value use %eax ; 返回标量或没有值的函数使用%eax ;
  • Functions returning floating point values use %st(0) ; 返回浮点值的函数使用%st(0) ;
  • For functions returning struct or union types, the caller provides space for the return value and passes its address as a hidden, first argument. 对于返回structunion类型的函数,调用者为返回值提供空间,并将其地址作为隐藏的第一个参数传递。 The callee returns this address in %eax . 被调用者在%eax返回此地址。

通常你会使用堆栈

If you're planning to interface with C or another higher-level language, typically you would accept the address of a memory buffer as an argument to your function and return your complex value by populating that buffer. 如果您计划与C或其他更高级语言进行交互,通常您会接受内存缓冲区的地址作为函数的参数,并通过填充该缓冲区来返回复杂值。 If this is assembly-only, then you can define your own convention using any set of registers you want, although usually you'd only do so if you have a specific reason (eg, performance). 如果这只是汇编,那么你可以使用你想要的任何寄存器来定义自己的约定,尽管通常只有在你有特定原因(例如性能)时才这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM