Your example works because of a Fortran-inspired (mis)feature of C (but not C++) called tentative definitions (6.9.2p2) which is commonly but nonstandardly extended to multiple files.
Tentative definitions are variable declarations without extern
and with
no initializer. In common implementations (pun intended), tentative definitions create a special kind of symbol which is called a common
symbol. During linking, in the presence of a regular symbol of the same name, other common symbols become references to the regular symbol which means all the empty VERSION
s in your translation units created there due to inclusions will become references to the regular symbol const char* const VERSION = "0.8 rev 213";
. If there's no such regular symbol, the common symbols will get merged into a single zero-initalized variable.
I don't know how to get a warning against this with a C compiler.
This
const char* const VERSION;
const char* const VERSION = "0.8 rev 213";
seems to work with gcc no matter what I've tried (g++
won't accept it -- C++ doesn't have the tentative definition feature and it doesn't like const variables that aren't explicitly initialized). But you can compile with -fno-common
(which is a fairly "common" (and highly recommended) nonstandard option (gcc,clang, and tcc all have it)) and then you will get a linker error if the non-initialized and the initialized extern-less declarations are in different translation units.
Example:
v.c:
const char * VERSION;
main.c
const char* VERSION;
int main(){}
compilation and linking:
gcc main.c v.c #no error because of tentative definitions
g++ main.c v.c #linker error because C++ doesn't have tentative definitions
gcc main.c v.c -fno-common #linker error because tentative defs. are disabled
(I removed the second const
in the example for the sake of the C++ example — C++ additionally makes const globals static or something like that which only complicates the demonstration of the tentative definition feature.)
With tentative definitions disabled or with C++, all your variable declarations in headers should have extern
in them
version.h:
extern const char* const VERSION;
and you should have exactly one definition for each global and that definition should have an initializer or be without extern
(some compilers will warn if you apply extern
to an initialized variable).
version.c:
#include "version.h" //optional; for type checking
const char* const VERSION = "0.8 rev 213";