4

当一个函数的按值返回直接按值传递给另一个函数时,我正在检查普通和非普通可复制类型之间的复制省略。对于非平凡的情况,似乎对象是按预期直接传输的,但对于平凡的情况,似乎输出对象被复制到堆栈上以生成第二个函数的输入对象。我的问题是,为什么?

如果这是意料之中的,那就令人惊讶了,因为非平凡可复制类型在这些函数之间传递的效率更高。

资源:

struct Trivial_Struct
{
    unsigned char bytes[ 4 * sizeof( void* ) ];
};

struct Nontrivial_Struct
{
    unsigned char bytes[ 4 * sizeof( void* ) ];
    Nontrivial_Struct( Nontrivial_Struct const& );
};

Trivial_Struct trivial_struct_source();
Nontrivial_Struct nontrivial_struct_source();
void trivial_struct_sink( Trivial_Struct );
void nontrivial_struct_sink( Nontrivial_Struct );

void test_trivial_struct()
{
    trivial_struct_sink( trivial_struct_source() );
}

void test_nontrivial_struct()
{
    nontrivial_struct_sink( nontrivial_struct_source() );
}

GCC 输出汇编:

test_trivial_struct():
    sub     rsp, 40
    mov     rdi, rsp
    call    trivial_struct_source()
    push    QWORD PTR [rsp+24]
    push    QWORD PTR [rsp+24]
    push    QWORD PTR [rsp+24]
    push    QWORD PTR [rsp+24]
    call    trivial_struct_sink(Trivial_Struct)
    add     rsp, 72
    ret
test_nontrivial_struct():
    sub     rsp, 40
    mov     rdi, rsp
    call    nontrivial_struct_source()
    mov     rdi, rsp
    call    nontrivial_struct_sink(Nontrivial_Struct)
    add     rsp, 40
    ret

神螺栓.org。我尝试了 GCC、Clang 和 MSVC;GCC 的程序集对我来说更容易阅读,但所有编译器似乎都为简单可复制的情况编写了类似的代码。

杂项:

  • 显然,如果我将类定义中Nontrivial_Struct( Nontrivial_Struct const& ) = default复制构造函数声明为;如果我在类定义之后添加Nontrivial_Struct::Nontrivial_Struct( Nontrivial_Struct const& ) = default; 那么它仍然很重要。
  • 我可以将“4”更改为较大的值,例如“64”,它仍然会发生。

推测:

4

1 回答 1

2

调用约定由 ABI 强制执行。ABI 指定两个源函数的返回值都由调用者分配,并传递一个隐藏指针。ABI 指定平凡结构在堆栈上传递,非平凡结构由隐藏指针传递。参考:x86-64C++ ABI。

[class.temporary]/3 为实现提供了为参数和返回值创建临时变量的自由度,这使得观察到的行为正常。它没有强制要求。

平凡的结构是在堆栈中初始化的返回值,并且必须在堆栈上传递(都是因为 ABI)。有人可能会问,为什么它将结构体从堆栈的第一个位置复制到堆栈的第二个位置?那个副本确实是不必要的。编译器可以做得更好。这是GCC 错误

于 2020-05-27T18:39:56.903 回答