I have some legacy code with some singleton classes that register themselves using constructors of global variables. It's a large codebase, that's compiled into one executable. I have tried to organize the code base and regroup code into libraries. A minimal example of the current code is
main.cpp
int main(int argc, char *argv[])
{
return 0;
}
Hash.cpp
#include <iostream>
class Hash
{
public:
Hash()
{
std::cout << "Hash\n";
}
};
Hash a;
and the current build configuration is
CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
project(mcve)
add_executable(mcve main.cpp Hash.cpp)
Building the code and running the executable prints
Hash
I've modified the build configuration to
cmake_minimum_required(VERSION 3.26)
project(mcve)
add_library(Hash Hash.cpp)
add_executable(mcve main.cpp)
target_link_libraries(mcve Hash)
This creates a static library libHash.a
and links it to the executable. Compiling the same code and running the executable doesn't print anything. Why is the difference, and where is it described? Is it part of the C++ standard or of the compiler? Is it OS specific (Linux static libraries)? Is it undefined behavior?
The difference should be described in your linker's documentation or introductory textbooks. You need to understand what static libraries are, how they work, and how to use them.
Without static libraries, all translation units — object code in a .o
file — are explicitly linked together and everything in them becomes a part of the executable. (This ignores dynamic libraries, which add unnecessary complication to this discussion.) In your first example both main.cpp
and hash.cpp
are linked into the mcve
executable, and at startup the sole global object from hash.cpp
gets constructed. The End.
Linking with a static library does not — I repeat does not — include everything from the static library into the executable. That's not how static libraries work. Only those translation units in the static library that export symbols which are undefined in the translation units linked with the static library are linked into the executable. (It's actually a bit more complicated, but the full complexity is immaterial for the purposes of this question and would only confuse things.) That's a defining characteristic of static libraries.
Close examination of the code shown reveals that there are no undefined or unresolved symbols from main.cpp
that are exported by hash.cpp
. So hash.cpp
is not linked into the executable, and since the global object you want is defined in hash.cpp
, it is not constructed when the program runs. The End.
It's not that the rules for initialization or construction change for static libraries. It's because there's nothing to initialize or construct.