考虑以下代码:
#include <array>
class C
{
std::array<char, 7> a{};
int b{};
};
C slow()
{
return {};
}
C fast()
{
C c;
return c;
}
GCC 6至9为slow()
产生了非常肿的代码:
slow():
xor eax, eax
mov DWORD PTR [rsp-25], 0
mov BYTE PTR [rsp-21], 0
mov edx, DWORD PTR [rsp-24]
mov DWORD PTR [rsp-32], 0
mov WORD PTR [rsp-28], ax
mov BYTE PTR [rsp-26], 0
mov rax, QWORD PTR [rsp-32]
ret
fast():
xor eax, eax
xor edx, edx
ret
两个功能在含义上有区别吗? Clang都发出类似fast()
的代码,而GCC 4-5比6-9做得更好,但也不是最优。
构建标志:-std=c++11 -O3
演示:https://godbolt.org/z/rPNG9o
根据以下反馈作为GCC错误提交:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90883
答案 0 :(得分:1)
GCC维护者同意这是一个错误(缺少优化),并且已在x86_64的主干中修复(ARM可能会在以后修复):https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90883
答案 1 :(得分:0)
这并不是真正的完整答案,但可能会提供线索。我怀疑fast
和slow
的含义之间存在细微差别,这可能会将编译器发送到不同的路径。如果将复制构造函数设为私有,则可以看到此信息。
#include <array>
class C
{
std::array<char, 7> a{};
public:
C(){}
private:
C(const C & c){}
};
// Compiles
C slow()
{
return {};
}
// Does not compile
C fast()
{
C c;
return c;
}
即使带有复制省略号fast
仍需要复制构造函数在slow
返回initialization list
的地方,该#include <array>
class C
{
std::array<char, 7> a{};
public:
C(){}
private:
C(const C & c){}
};
C slow()
{
return {};
}
C fast()
{
return C();
}
由调用者显式构造返回值。这些可能会或可能不会做同样的事情,但我相信编译器必须进行一些操作才能确定是否是这种情况。
有一篇详细的博客文章,提供了有关此主题的一些有趣背景
https://akrzemi1.wordpress.com/2018/05/16/rvalues-redefined/
但是行为在C ++ 17中已更改
而
fast
return C()
在C ++ 11下无法编译,而现在在C ++ 17下编译
原因是C fast(){
C c;
return c;
}
的含义从返回临时对象变为在调用者框架中显式构造对象。
所以现在在C ++ 17中,两者之间有很大的区别
C fast(){
return C();
}
和
{{1}}
因为在第二个示例中,您甚至不需要复制或移动构造函数就可以使用。
绝对不是C ++ 101
答案 2 :(得分:0)
这两个函数是等效的:返回的对象(更确切地说,是对这些函数的假设调用的结果对象)通过使用其默认成员初始化程序初始化每个成员来初始化。
slow
:{}
作为初始值设定项(stmt.return)进行了复制初始化。 =>因此,调用slow
的结果对象的所有成员都将使用其默认成员初始化程序dcl.init.aggr]/5.4进行初始化。
fast
:C(){}
([class.default.ctor]/4) =>因此,调用slow
的结果对象的所有成员都将使用其默认成员初始化程序[class.base.init]/9.1
这两个函数的结果程序集在功能上是等效的。因此,Gcc生产的组件符合标准。
在慢速情况下,汇编仅次优。对象相应地返回到两个寄存器上的SystemV x86 abi:rax和rdx(edx)。首先,它将栈中位于地址[rsp-32]上类C的概念上的对象清零。将a
与a
和b
之间的填充字节清零。然后,它将已初始化的堆栈部分复制到寄存器中。将堆栈归零的方式只是次优的,所有这些操作等效于b
程序集的2个xor操作。所以那只是一个明显的错误。