I am having a problem with some C++20 code that is crashing when compiled in release (-O3, clang 15), and it's very tricky to debug due to a number of obfuscation techniques being applied on the final executable which makes it extremely difficult to see the actual x86_64 ASM being executed.
Still, I have managed to narrow the crash down to the following pseudo code...
for (std::wstring& data : datas)
{
auto id = std::format(L"foo_{0}", data);
do_something(id, std::move(data));
}
...where the signature for do_something
looks like this:
void do_something(const std::wstring&, std::wstring);
Basically, the move constructor of the std::wstring
for the second argument should be invoked already when setting up the call, I assume.
Now, this code works fine with -O2, but when enabling -O3 it breaks, and I have a hunch.
Reading through the C++ standard, we can see the following...
Order of evaluation of any part of any expression, including order of evaluation of function arguments is unspecified (with some exceptions listed below). The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.
I have also read that C++ compilers are free to optimize away locals when they have no side effects (but have not located this specific rule in the standards).
Could it be that, since std::format
in itself has no side effect in the above code, the compiler "inlines" it into the function call when using -O3? And when it does, the order of evaluation is undefined, so that the move actually happens before the call to std::format
is called?
Basically what I am asking is: would the optimization I describe be permitted by the C++ standard?
Could it be that, since std::format in itself has no side effect in the above code, the compiler "inlines" it into the function call when using -O3?
No. Or if it does, it will keep the order of instructions such that std::format
is called before the move (the second parameter's move constructor, not the std::move
which is not an instruction). Otherwise, what is well-defined behavior would turn into undefined behavior, and that is not something a compiler is allowed to do in normal situations.
A compiler can provide options that break standard compliance. This is the case with -Ofast
for example. However, -O3
does not break standard compliance, and as far as I know, there is no GCC option that would result in what you describe.