Skip to content

92 Advanced The Hundred

The Global That Was Not Global

five calls, and nobody counted to five

// counter.hpp
#pragma once
static int hits = 0;
static void hit() { ++hits; }

// other.cpp
#include "counter.hpp"
void work() {
    hit();
    hit();
    hit();
}
int hits_in_other() { return hits; }

// main.cpp
#include "counter.hpp"

void work();   // other.cpp — three more hits
int hits_in_other();

int main() {
    hit();
    hit();
    work();
    std::cout << "hit() was called 5 times\n";
    std::cout << "main.cpp  sees hits = " << hits << "\n";
    std::cout << "other.cpp sees hits = " << hits_in_other() << "\n";
}

Build it: g++ -std=c++17 main.cpp other.cpp -o demo. What are the two numbers?

Answer

2 and 3. Neither file sees 5 — the header handed each .cpp a counter of its own.

Why

At namespace scope static does not mean "one shared instance" — it means internal linkage, private to this translation unit. #include is plain text, so every .cpp that pulls in counter.hpp compiles its own hits; nm demo lists two. The link succeeds because private copies cannot collide: delete the static and you get a multiple definition error, a bug that at least announces itself. static void hit() is duplicated too — that is how a header of small static helpers quietly fattens every object file. -Wall -Wextra never says a word.

The fix

Say inline: since C++17 the header may own the definition — one object, five hits.

inline int hits = 0;
inline void hit() { ++hits; }

Before C++17: extern int hits; in the header, int hits = 0; in exactly one .cpp.

Takeaway: static in a header multiplies the variable instead of sharing it.

Try it: g++ -std=c++17 main.cpp other.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry

This lesson also needs counter.hpp, other.cpp; Compiler Explorer opens main.cpp only.