Skip to content

18 Beginner The Hundred

The Variable That Is Zero on Tuesdays

one function, called twice, disagreeing with itself

int scoreboard;

int bonus() {
    int points;
    for (int i = 0; i < 3; ++i)
        points += 5;
    return points;
}

void play_round() {
    for (int i = 0; i < 32; ++i)
        scoreboard += 1 + (i % 6);
}

int main() {
    int before = bonus();
    play_round();
    int after = bonus();   // same function, same answer... right?
    std::cout << "scoreboard   = " << scoreboard << '\n';
    std::cout << "bonus before = " << before << '\n';
    std::cout << "bonus after  = " << after << '\n';
}

Run it. Do the two bonus lines agree?

Answer

They don't. Typical output (GCC 11.5 on x86-64, no -O flag):

scoreboard   = 108
bonus before = 15
bonus after  = 47

Why

Both accumulators forget to start at zero; only one gets away with it. scoreboard has static storage duration, so it is zero-initialized before main — guaranteed, and the tally comes out right. points is a local: indeterminate, and reading it is undefined behavior. Unoptimized, bonus and play_round share a stack slot, so the second call inherits the 32 the loop counter left there; at -O2 GCC 11.5 prints 15 on both lines and Clang 18 identical junk on both — the value is whatever the build leaves behind.

C++26 (P2795) reclassifies this read as erroneous behaviour rather than UB — an implementation-supplied value, diagnostics encouraged. GCC 11.5 stays silent at -O0 even with -Wall -Wextra, then reports 'points' is used uninitialized [-Wuninitialized] from -O1 up; Clang 18 warns already at -O0.

The fix

int points = 0;   // or int points{}; — either way, say it at the declaration

Takeaway: only static and thread-local variables are zeroed for free — a plain local holds whatever the build leaves behind.

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

Open in Compiler Explorer ↗ Quiz this entry