Static Initialization and Destruction of a Static Library's Globals Not Happening with G++

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



Leave a reply



Submit