Skip to content

33 Intermediate The Hundred

The Struct That Forgot Its Zero

two points, two braces apart

struct Point {
    int x;
    int y;
};

void show_canvas() {
    Point size{800, 600};
    std::cout << "canvas: " << size.x << " x " << size.y << "\n";
}

void show_points() {
    Point a;   // no constructor to run, so... zero?
    Point b{};
    std::cout << "a = (" << a.x << ", " << a.y << ")\n";
    std::cout << "b = (" << b.x << ", " << b.y << ")\n";
}

int main() {
    show_canvas();
    show_points();
}

Run it. Are a and b the same point?

Answer

No. Typical output (GCC 11.5 and Clang 18.1, x86-64, no -O flag) — a varies by build:

canvas: 800 x 600
a = (800, 600)
b = (0, 0)

Why

Point a; is default-initialization: it runs the implicit default constructor, which for these two ints is trivial and does nothing at all, so a keeps whatever bytes were there and reading them is undefined behavior. Point b{} value-initializes, zeroing both; the heap splits the same way (new Point vs new Point()). Stale bytes are no promise: at -O0 a inherits show_canvas's size, but from -O1 up GCC exploits the undefined read and prints a = (0, 0), while Clang prints stack junk — a different number under Valgrind than without. One twist: a user-provided Point() {} stops b{} zeroing.

C++26 (P2795) reclassifies this read as erroneous behaviour rather than UB — an implementation-supplied value, diagnostics encouraged. GCC's -Wall -Wextra flags it (-Wmaybe-uninitialized at -O0, -Wuninitialized above); Clang 18.1 says nothing at any level, and Valgrind catches both -O0 builds but neither optimized one.

The fix

Point a{};                                // value-initialized: (0, 0), every time
struct Point { int x = 0; int y = 0; };   // or bake the zeros into the type

Takeaway: {} is what turns default-initialization into value-initialization.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo, then again with -O1

Open in Compiler Explorer ↗ Quiz this entry