C++ 关于在C/C++/装配

C++ 关于在C/C++/装配,c++,assembly,optimization,x86,return-value,C++,Assembly,Optimization,X86,Return Value,我已经阅读了一些关于返回多个值的问题,例如,和 我同意大多数用来证明不止一个返回值不是绝对必要的论点,我理解为什么没有实现这样的功能,但我仍然不理解为什么我们不能使用多个调用方保存的寄存器(如ECX和EDX)来返回这样的值 使用寄存器而不是创建一个类/结构来存储这些值或通过引用/指针传递参数(两者都使用内存来存储它们)会更快吗?如果可以这样做,是否有C/C++编译器使用此功能来加速代码 编辑: 理想的代码如下所示: (int, int) getTwoValues(void) { return 1

我已经阅读了一些关于返回多个值的问题,例如,和

我同意大多数用来证明不止一个返回值不是绝对必要的论点,我理解为什么没有实现这样的功能,但我仍然不理解为什么我们不能使用多个调用方保存的寄存器(如ECX和EDX)来返回这样的值

使用寄存器而不是创建一个类/结构来存储这些值或通过引用/指针传递参数(两者都使用内存来存储它们)会更快吗?如果可以这样做,是否有C/C++编译器使用此功能来加速代码

编辑: 理想的代码如下所示:

(int, int) getTwoValues(void) { return 1, 2; }

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

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

是的,有时会这样做。如果您阅读了cdecl下的维基百科页面:

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

(强调矿山)


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

在堆栈中返回并不一定慢,因为一旦值在一级缓存中可用(堆栈通常会满足),访问它们就会非常快

然而,在大多数计算机体系结构中,至少有两个寄存器返回的值是字长的两倍(或两倍以上)(x86中的
edx:eax
,x86_64中的
rdx:rax
,MIPS(),ARM1中的
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发出以下指令

对于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

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

如果
返回
a
struct
,您可以在逻辑上
返回多个值,我不是要返回一个指向内存中的结构的指针,该结构将用于在函数返回后获取值吗?well fastcall使用ecx和edx中前2个参数的寄存器,并返回eax,其速度不会比默认的cdecl调用约定快多少。一些基准测试甚至显示fastcall速度较慢。您是否有生成更差机器代码的示例代码(在优化版本中)@user2565020这是一个由编译器决定的实现细节,但在堆栈上返回值实际上比在EAX和ECX上返回两个int值要慢。Lua不会在“struct”中包装多个返回值;它有一个堆栈,并将所有返回值推送到该堆栈上。你是不是把
返回a,b,c
返回{a,b,c}
搞混了?说得好,但我们在争论语义。如果它是一个结构或不是,它将是相同的。所有值都以相同的方式推送到堆栈上。至于通过寄存器返回,您正在为那个微不足道的优化量而分心。是的,寄存器速度更快,但如果你真的检查时钟周期的数量并进行比较,然后转换为实时,我们谈论的是一个不可估量的时间节省。编辑:我只是查了一下,推操作和移动到寄存器的时钟周期几乎相同,所以实际上没有任何区别。顺便说一句,我检查了4个不同的处理器,差异约为1-2个时钟周期,平均(基于相同的时钟速度)节省了约0.0000000001秒的时间(我希望我在里面放了足够多的0).如果我理解正确,结构是否转换为适合2个寄存器?如果是,是否可以使用2个以上的寄存器?数据结构需要适合2个寄存器。如果2个寄存器没有提供足够的位来存储整个结构,则需要使用内存来传递结构。但这是针对此特定调用约定的。编译器可以使用任意多的寄存器
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