我遇到了一个问题,一些C++20代码在发布时编译时崩溃(-O3,clang 15),并且由于在最终可执行文件上应用了许多混淆技术,因此调试非常棘手,这使得很难看到实际执行的x86_64 ASM。
不过,我还是设法将崩溃范围缩小到以下伪代码...
for (std::wstring& data : datas)
{
auto id = std::format(L"foo_{0}", data);
do_something(id, std::move(data));
}
字符串
...其中do_something
的签名如下所示:
void do_something(const std::wstring&, std::wstring);
型
基本上,我假设第二个参数的std::wstring
的移动构造函数应该在设置调用时就已经被调用了。
现在,这段代码在-O2下工作正常,但是当启用-O3时,它就坏了,我有预感。
阅读C标准,我们可以看到以下内容。
任何表达式的任何部分的求值顺序,包括函数参数的求值顺序都是未指定的(下面列出了一些例外情况)。编译器可以按任何顺序计算操作数和其他子表达式,并且在再次计算同一表达式时可以选择其他顺序。
我也读到过C编译器在没有副作用的情况下可以自由地优化掉局部变量(但是在标准中没有找到这个特定的规则)。
会不会是因为std::format
本身在上面的代码中没有副作用,所以编译器在使用-O3时将其“内联”到函数调用中?当它这样做时,求值的顺序是未定义的,所以移动实际上发生在 * 之前 * 调用std::format
?
基本上我想问的是:我所描述的优化是C++标准所允许的吗?
1条答案
按热度按时间fkaflof61#
会不会是因为std::format本身在上面的代码中没有副作用,所以编译器在使用-O3时将其“内联”到函数调用中?
否。或者,如果是,它将保持指令的顺序,以便在移动之前调用
std::format
(第二个参数的移动构造函数,而不是指令的std::move
)。否则,定义良好的行为将变成未定义的行为,这是编译器在正常情况下不允许做的事情。编译器可以提供违反标准遵从性的选项。例如,
-Ofast
就是这种情况。然而,-O3
并没有破坏标准遵从性,据我所知,没有GCC选项会导致您所描述的结果。