[英]Why does GCC fail to optimize unless the return value has a name?
考虑以下代码:
#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()
:
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
GCC维护人员同意这是一个错误(缺少优化),并且已在x86_64的主干中修复(ARM可能会在以后修复): https : //gcc.gnu.org/bugzilla/show_bug.cgi?id=90883
这并不是真正的完整答案,但可能会提供一个线索。 我怀疑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
,该initialization list
由调用者显式构造返回值。 这些最终可能会或可能不会做同样的事情,但是我相信编译器必须进行一些操作才能确定是否是这种情况。
有一篇详细的博客文章提供了有关此主题的一些有趣背景
https://akrzemi1.wordpress.com/2018/05/16/rvalues-redefined/
但是,该行为在C ++ 17中已更改
鉴于
#include <array>
class C
{
std::array<char, 7> a{};
public:
C(){}
private:
C(const C & c){}
};
C slow()
{
return {};
}
C fast()
{
return C();
}
fast
将无法在C ++ 11下编译,而现在可以在C ++ 17下编译
原因是, return C()
的含义从返回临时值变为在调用者框架中显式构造对象。
所以现在在C ++ 17中,
C fast(){
C c;
return c;
}
和
C fast(){
return C();
}
因为在第二个版本中,您甚至不需要复制或移动构造函数就可以使用。
绝对不是C ++ 101
这两个函数是等效的 :通过使用默认成员初始化程序初始化每个成员,可以初始化返回的对象(更准确地说,是对这些函数的假设调用的结果对象)。
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
。 然后,它将已初始化的堆栈部分复制到寄存器中。 将堆栈清零的方式只是次优,所有这些操作都等同于fast
组装的2 xor操作。 因此,这只是一个明显的错误。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.