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.
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
This lesson also needs counter.hpp, other.cpp; Compiler Explorer opens main.cpp only.