Let's consider the following code (Compiler Explorer):
#include <array>
#include <cstddef>
#include <new>
struct Data
{
//Data() {} // TODO
int value;
};
auto test()
{
alignas(Data) std::array<std::byte, sizeof(Data)> buffer;
buffer.fill(std::byte{0xFF});
auto* const p = new(buffer.data()) Data;
const auto value = p->value;
p->~Data();
return value;
}
What I would expect from this code is that:
sizeof(Data)
bytes is created and filled with 0xFF
.Data
is constructed in it without initializing any value.-1
is returned from test
.Judging from the generated assembly on Compiler Explorer all three compilers:
indeed, do so. In the examples I took newest compiler versions and C++17 but if language version matters here, I'm eager to hear.
Now, if we uncomment the empty Data
constructor (Compiler Explorer):
Data() {} // TODO
Nothing changes for
but for GCC we get -Wuninitialized
warning and the generated code returns now 0
(rather than -1
) which looks like the compiler considers this Undefined Behavior and just did whatever.
Is GCC correct in considering this (I presume) Undefined Behavior and issuing -Wuninitialized
? Or is Clang and MSVC correct in considering this OK?
Formally, when you create a new object, its value is initially always indeterminate (before C++26). It doesn't matter whether the memory where the new object is placed already had some bytes written to it beforehand.
So in p->value
the value of value
is indeterminate, because p
refers to a new Data
object that you created in the previous line. Whatever you did with that memory at that location previously doesn't matter. Reading the indeterminate value of type int
causes undefined behavior.
Whether or not you define the default constructor doesn't matter either. It doesn't initialize or modify the member value
with another value either way.
What you want to do here requires std::start_lifetime_as
:
auto* const p = std::start_lifetime_as<Data>(&buffer);