简体   繁体   English

平凡与非平凡类型的复制省略差异

[英]Difference in Copy Elision for Trivial vs. Non-trivial Types

I'm inspecting copy-elision between trivial and non-trivial copy-able types when one function's return by value directly passes by value into another function.当一个函数按值返回直接按值传递给另一个函数时,我正在检查普通和非普通可复制类型之间的复制省略。 For the non-trivial case, it appears the object is directly transferred as expected, but for the trivial case, it appears the output object is copied on the stack to make the input object for the second function.对于非平凡的情况,对象似乎是按预期直接传输的,但对于平凡的情况,似乎输出对象被复制到堆栈上以制作第二个函数的输入对象。 My question is, why?我的问题是,为什么?

If this is expected, this is surprising, as the non-trivially copy-able type is more efficiently passed between these functions.如果这是预料之中的,这令人惊讶,因为在这些函数之间更有效地传递了非平凡可复制类型。

Source:来源:

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 Output Assembly: 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

godbolt.org . Godbolt.org I tried GCC, Clang, and MSVC;我尝试了 GCC、Clang 和 MSVC; GCC's assembly is easier for me to read, but all compilers seems to make similar code for the trivially copy-ably case. GCC 的程序集对我来说更容易阅读,但所有编译器似乎都为简单可复制的情况制作了类似的代码。

Misc:杂项:

  • Apparently, I can accidentally make 'Nontrivial_Struct' actually be trivial if I declare the copy constructor inside the class definition as Nontrivial_Struct( Nontrivial_Struct const& ) = default ;显然,我不小心让“Nontrivial_Struct”其实是微不足道的,如果我声明类定义里面的拷贝构造函数Nontrivial_Struct( Nontrivial_Struct const& ) = default ; if I add Nontrivial_Struct::Nontrivial_Struct( Nontrivial_Struct const& ) = default;如果我添加Nontrivial_Struct::Nontrivial_Struct( Nontrivial_Struct const& ) = default; after the class definition then it remains non-trivial.类定义之后,它仍然是重要的。
  • I can change the '4' to large values, such as '64', and it still occurs.我可以将“4”更改为较大的值,例如“64”,但它仍然发生。

Speculation:猜测:

The calling convention is mandated by the ABI.调用约定由 ABI 强制执行。 The ABI specifies that both the source functions' return values are allocated by the caller and a hidden pointer is passed. ABI 指定两个源函数的返回值都由调用者分配,并传递一个隐藏的指针。 The ABI specifies that the trivial struct is passed on the stack and the nontrivial one is passed by hidden pointer. ABI 指定平凡结构在堆栈上传递,非平凡结构通过隐藏指针传递。 Reference: x86-64 and C++ ABIs.参考: x86-64C++ ABI。

[class.temporary]/3 gives implementations latitude to create temporaries for arguments and return values, which makes the observed behavior OK. [class.temporary]/3 为实现提供了为参数和返回值创建临时变量的自由度,这使得观察到的行为正常。 It does not mandate it.它没有强制要求。

The trivial struct is the return value which is initialized in the stack and must be passed on the stack (both because of ABI).简单的结构是在堆栈中初始化的返回值,并且必须在堆栈上传递(都是因为 ABI)。 One might ask, why does it copy the struct from its first location on the stack to the second location on the stack?有人可能会问,为什么它将结构从它在堆栈上的第一个位置复制到堆栈上的第二个位置? That copy is indeed unnecessary.那个副本确实是不必要的。 The compiler could do better.编译器可以做得更好。 Here's the GCC bug .这是GCC 错误

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM