关于在C / C ++ / Assembly中返回多个值

我已经阅读了一些关于返回多个值的问题,例如在C ++和Java中只有一个返回值的原因是什么? , 从C ++函数和https://softwareengineering.stackexchange.com/questions/203471/why-do-most-programming-languages-only-support-returning-a-single-value-from-af 返回多个值 。

我同意用于certificate多个返回值不是绝对必要的大多数参数,我理解为什么没有实现这样的function,但我仍然无法理解为什么我们不能使用多个调用者保存的寄存器例如ECX和EDX返回这样的值。

使用寄存器而不是创建一个Class / Struct来存储这些值或通过引用/指针传递参数不是更快,两者都使用内存来存储它们吗? 如果可以做这样的事情,那么任何C / C ++编译器是否都使用此function来加速代码?

编辑:理想的代码是这样的:

(int, int) getTwoValues(void) { return 1, 2; } int main(int argc, char** argv) { (int a, int b) = getTwoValues();//a and b are actually returned in registers so future operations with a and b are faster //do something with a and b return 0; } 

是的,这有时会完成。 如果您在cdecl下阅读x86调用约定的Wikipedia页面:

cdecl的解释有一些变化,特别是如何返回值。 因此,为不同的操作系统平台和/或不同的编译器编译的x86程序可能是不兼容的,即使它们都使用“cdecl”约定并且不调用底层环境。 一些编译器在寄存器对EAX:EDX中返回长度为2个寄存器或更少寄存器的简单数据结构 ,并且需要exception处理程序(例如,定义的构造函数,析构函数或赋值)进行特殊处理的较大结构和类对象返回记忆。 为了传递“在内存中”,调用者分配内存并将指针作为隐藏的第一个参数传递给它; 被调用者填充内存并返回指针,返回时弹出隐藏的指针。

(强调我的)

最终,它归结为呼叫惯例。 您的编译器可以优化您的代码以使用它想要的任何寄存器,但是当您的代码与其他代码(如操作系统)交互时,它需要遵循标准调用约定,这通常使用1个寄存器来返回值。

堆栈中的返回不一定更慢,因为一旦值在L1高速缓存中可用(堆​​栈经常满足),访问它们将非常快。

但是在大多数计算机体系结构中,至少有 2个寄存器返回的值是字大小的两倍(或更多) (x86中的edx:eax ,x86_64中的rdx:rax ,MIPS中的$v0$v1 )( 为什么选择MIPS)汇编器有多个寄存器用于返回值? ), R0:R3 ARM 1中的 R0:R3 ,ARM64中的X0:X7 ……)。 那些没有的主要是微控制器,只有一个累加器或非常有限数量的寄存器。

1 “如果返回的值的类型太大而不适合r0到r3,或者在编译时无法静态确定其大小,则调用者必须在运行时为该值分配空间,并将指针传递给该空间R0“。

这些寄存器还可用于直接返回小型结构,这些结构适合2或更多(取决于架构和ABI)寄存器或更少。

例如,使用以下代码

 struct Point { int x, y; }; struct shortPoint { short x, y; }; struct Point3D { int x, y, z; }; Point P1() { Point p; px = 1; py = 2; return p; } Point P2() { Point p; px = 1; py = 0; return p; } shortPoint P3() { shortPoint p; px = 1; py = 0; return p; } Point3D P4() { Point3D p; px = 1; py = 2; pz = 3; return p; } 

如您所见,Clang会针对x86_64发出以下指令

 P1(): # @P1() movabs rax, 8589934593 ret P2(): # @P2() mov eax, 1 ret P3(): # @P3() mov eax, 1 ret P4(): # @P4() movabs rax, 8589934593 mov edx, 3 ret 

对于ARM64:

 P1(): mov x0, 1 orr x0, x0, 8589934592 ret P2(): mov x0, 1 ret P3(): mov w0, 1 ret P4(): mov x1, 1 mov x0, 0 sub sp, sp, #16 bfi x0, x1, 0, 32 mov x1, 2 bfi x0, x1, 32, 32 add sp, sp, 16 mov x1, 3 ret 

如您所见,不涉及堆栈操作。 您可以切换到其他编译器,以查看值主要在寄存器上返回。

返回数据放在堆栈上。 通过复制返回结构与返回多个值完全相同,因为它将所有数据成员放在堆栈中。 如果您想要多个返回值,这是最简单的方法。 我知道在Lua中它正是它如何处理它,只是将它包装在一个结构中。 为什么它从未实现过,可能是因为你可以用结构来实现它,所以为什么要实现不同的方法呢? 至于C ++,它实际上支持多个返回值,但它是一个特殊类的forms,就像Java处理多个返回值(元组)一样。 所以最后,它都是一样的,要么你复制数据原始(非指针/非引用结构/对象),要么只是复制指向存储多个值的集合的指针。