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

时间:2015-07-19 01:30:42

标签: c++ c assembly x86

我已阅读一些关于返回多个值的问题,例如What is the reason behind having only one return value in C++ and Java?Returning multiple values from a C++ functionhttps://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;
}

3 个答案:

答案 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 $v1Why MIPS assembler has more that one register for return value?),ARM 1 中为R0:R3,ARM64中为X0:X7。 ..)。那些没有的主要是微控制器,只有一个累加器或非常有限的寄存器。

1 <子> "If the type of value returned is too large to fit in r0 to r3, or whose size cannot be determined statically at compile time, then the caller must allocate space for that value at run time, and pass a pointer to that space in r0."

这些寄存器也可用于直接返回小于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处理多个返回值(元组)的方式实际上相同。所以最后,它都是相同的,要么你复制数据原始(非指针/非引用结构/对象),要么只是复制指向存储多个值的集合的指针。