我已阅读一些关于返回多个值的问题,例如What is the reason behind having only one return value in C++ and Java?,Returning multiple values from a C++ function和https://softwareengineering.stackexchange.com/questions/203471/why-do-most-programming-languages-only-support-returning-a-single-value-from-a-f。
我同意大多数用于证明不止一个返回值不是绝对必要的论据,我理解为什么这样的功能还没有实现,但我仍然无法理解为什么可以&#39 ;我们使用多个调用者保存的寄存器(如ECX和EDX)来返回这些值。
使用寄存器而不是创建一个Class / Struct来存储这些值或通过引用/指针传递参数,这两者都使用内存来存储它们会更快吗?如果可以做这样的事情,那么任何C / C ++编译器是否都使用此功能来加速代码?
编辑:理想的代码是这样的:
(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;
}
答案 0 :(得分:8)
是的,有时会这样做。如果您在cdecl:{/ p>下的x86 calling conventions上阅读维基百科页面
cdecl的解释有一些变化,特别是如何返回值。因此,为不同的操作系统平台和/或不同的编译器编译的x86程序可能是不兼容的,即使它们都使用" cdecl"约定,不要呼唤底层环境。 一些编译器在寄存器对EAX:EDX 中返回长度为2个寄存器或更少的简单数据结构,以及需要异常处理程序进行特殊处理的较大结构和类对象(例如,定义的构造函数,析构函数) ,或赋值)在内存中返回。要在内存中传递"",调用者分配内存并将指针作为隐藏的第一个参数传递给它;被调用者填充内存并返回指针,返回时弹出隐藏的指针。
(强调我的)
归根结底,它归结为调用约定。您的编译器可以优化您的代码以使用它想要的任何寄存器,但是当您的代码与其他代码(如操作系统)交互时,它需要遵循标准调用约定,通常使用1个寄存器返回值。
答案 1 :(得分:3)
在堆栈中返回并不一定会慢一些,因为一旦这些值在L1缓存中可用(堆栈经常满足),访问它们将非常快。
然而,在大多数计算机体系结构中,至少 2个寄存器返回的值是字大小的两倍(或更多)(edx:eax
在x86中,x86_64中为rdx:rax
,MIPS中为$v0
and $v1
(Why MIPS assembler has more that one register for return value?),ARM 1 中为R0:R3
,ARM64中为X0:X7
。 ..)。那些没有的主要是微控制器,只有一个累加器或非常有限的寄存器。
这些寄存器也可用于直接返回小于2(或更多,取决于架构和ABI)寄存器或更少寄存器的小结构。
例如,使用以下代码
struct Point
{
int x, y;
};
struct shortPoint
{
short x, y;
};
struct Point3D
{
int x, y, z;
};
Point P1()
{
Point p;
p.x = 1;
p.y = 2;
return p;
}
Point P2()
{
Point p;
p.x = 1;
p.y = 0;
return p;
}
shortPoint P3()
{
shortPoint p;
p.x = 1;
p.y = 0;
return p;
}
Point3D P4()
{
Point3D p;
p.x = 1;
p.y = 2;
p.z = 3;
return p;
}
Clang会针对x86_64发出以下说明,因为您可以看到here
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
如您所见,不涉及堆栈操作。您可以切换到其他编译器,以查看值主要在寄存器上返回。
答案 2 :(得分:1)
返回数据放在堆栈上。通过复制返回结构与返回多个值完全相同,因为它将所有数据成员放在堆栈中。如果您想要多个返回值,这是最简单的方法。我在Lua中知道它究竟是如何处理它的,只需将它包装在一个结构中。为什么它从未实现过,可能是因为你可以用结构来实现它,那么为什么要实现不同的方法呢?至于C ++,它实际上支持多个返回值,但它以特殊类的形式存在,与Java处理多个返回值(元组)的方式实际上相同。所以最后,它都是相同的,要么你复制数据原始(非指针/非引用结构/对象),要么只是复制指向存储多个值的集合的指针。