c++gccvisual-c++clanglanguage-lawyer

Is GCC right with -Wuninitialized?


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:

  1. A correctly aligned buffer of sizeof(Data) bytes is created and filled with 0xFF.
  2. Data is constructed in it without initializing any value.
  3. Hence, it retains the original memory of the buffer itself.
  4. And so, under typical circumstances -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?


Solution

  • 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);