Static initialization and destruction of a static library's globals not happening with g++
.a
static libraries contain several .o
but they are not linked in unless you reference them from the main app..o
files standalone link always.
So .o
files in the linker always go inside, referenced or not, but from .a
files only referenced .o
object files are linked.
As a note, static global objects are not required to be initialized till you actually reference anything in the compilation unit, most compilers will initialize all of them before main, but the only requirement is that they get initialized before any function of the compilation unit gets executed.
static initializer is optimized away when it is in a library
So because of the comments on the question that finally led me into the right direction. The problem is that the function is in a static library and there the code is not called if it is not used in the main application, because the linker doesn't even add the code to the executable.
For a more detailed description I found this question:
Static initialization and destruction of a static library's globals not happening with g++
In order to force the code being called, I have to either move the registration into a module that will be needed for sure (and thus gets linked in), or use the linker option -Wl,--whole-archive
.
ld linker question: the --whole-archive option
LTO optimizing out global variables
OK, I did some digging and the fact you're linking the .a library is the culprit here, not the LTO, neither any other optimization.
This had been brought up on SO before btw, see: Static initialization and destruction of a static library's globals not happening with g++
When linking the .o files (as I did on godbolt) everything goes in and it works.
For .a files only the referenced code is linked, the rest is not. Creating a dummy variable is one workaround, but the proper one is passing --whole-archive
to the linker.
I could not run your makefile-based example due to issues with libtool, but have a look at my CMake config:
cmake_minimum_required(VERSION 3.18)
project(LINK)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
add_library(Files File1.cpp File2.cpp)
target_include_directories(Files
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_compile_definitions(Files PUBLIC ${FORCE})
add_executable(test Foo.cpp main.cpp Registrar.cpp)
# note the line below
target_link_libraries(test -Wl,--whole-archive Files -Wl,--no-whole-archive)
target_compile_definitions(test PUBLIC ${FORCE})
When linking it will invoke the command the more-less the following way:
g++ -o test -Wl, --whole-archive -l:libFiles.a -Wl, --no-whole-archive Foo.o Registrar.o main.o
Destruction order between globals and static function locals
It is well-known that objects with static storage duration are destroyed in the reverse order of their construction. But to be more specific—and since it matters in your case—they are destroyed in the reverse order of the completion of their initialization. See [basic.start.term]/3.
In your case, the initialization of o
will complete before the initialization of e
. Therefore, e
is destroyed before o
.
trying to force static object initialization
This is a tricky area of C++. What you've done is to try to define the static member here:
template<> FactoryHelper<ClassName##Factory> FactoryHelper<ClassName##Factory>::_helper;\
but this is actually a declaration and not a definition. For C++ to treat it as a definition you have to pass something to the constructor. Typically, this is the value you want to initialize it to:
template<> FactoryHelper<ClassName##Factory> FactoryHelper<ClassName##Factory>::_helper = FactoryHelper<ClassName##Factory>();\
But in your case, you want this to be a singleton, so you probably don't want it to be copyable. In that case, you need some dummy parameter:
template<> FactoryHelper<ClassName##Factory> FactoryHelper<ClassName##Factory>::_helper(0);\
and you have to modify your constructor appropriately:
template<> FactoryHelper<ClassName##Factory>::FactoryHelper (int) { std::cout << "object initialized!" << std::endl; }\
Here is the complete working example:
#include <iostream>
namespace my_lib
{
template<typename> struct FactoryBase { };
template <typename T>
struct FactoryHelper
{
FactoryHelper (int);
static FactoryHelper<T> _helper;
};
}
#define CREATE_FACTORY(ClassName)\
namespace my_lib\
{\
class ClassName##Factory;\
template<> FactoryHelper<ClassName##Factory>::FactoryHelper (int) { std::cout << "object initialized!" << std::endl; }\
template<> FactoryHelper<ClassName##Factory> FactoryHelper<ClassName##Factory>::_helper(0);\
struct ClassName##Factory : public FactoryBase<ClassName> {\
};\
}
struct UnitTestExample {
};
CREATE_FACTORY(UnitTestExample);
int main(int argc,char **argv)
{
return 0;
}
That said, using some of the suggestions in the other answers may be a better design decision.
More information on the explicit specialization declaration vs. definition can be found here: static member initialization for specialized template class
Destruction order of static objects in C++
The static objects are destructed in the reverse order of construction. And the order of construction is very hard to control. The only thing you can be sure of is that two objects defined in the same compilation unit will be constructed in the order of definition. Anything else is more or less random.
Related Topics
Add Library Search Path to Clang
How to Resize an Image to a Specific Size in Opencv
G++ Ld: Symbol(S) Not Found for Architecture X86_64
Should Function Declarations Include Parameter Names
Advantages of Using Arrays Instead of Std::Vector
Using Std::Variant with Recursion, Without Using Boost::Recursive_Wrapper
How to Load Bmp File Using X11 Window Background
Send Binary File Over Tcp/Ip Connection
How to Reproduce Tcp Protocol 3-Way Handshake with Raw Sockets Correctly
Under What Circumstances Is It Advantageous to Give an Implementation of a Pure Virtual Function
How to Use a Constexpr Value in a Lambda Without Capturing It
Why Derive from a Concrete Class Is a Poor Design
What's the Difference Between C and C++
Are There in X86 Any Instructions to Accelerate Sha (Sha1/2/256/512) Encoding
Can You Make a Computed Goto in C++
Linux C++: Does a Return from Main() Cause a Multithreaded App to Terminate