Last Update:
Static Variables Initialization in a Static Library, Example
Table of Contents
This post is motivated by one important comment from my last article about factories and self-registering types:
(me) So the compiler won’t optimize such variable.
Yet, unfortunately, the linker will happily ignore it if linking from a static library.
So… what’s the problem with the linker?
Intro
The main idea behind self-registering types is that each class need to register in the factory. The factory doesn’t know all the types upfront.
In my proposed solution you have to invoke the following code:
bool ZipCompression::s_registered =
CompressionMethodFactory::Register(ZipCompression::GetFactoryName(),
ZipCompression::CreateMethod);
s_registered
is a static boolean variable in the class. The variable
is initialized before main()
kicks in and later you have all the types
in the factory.
In the above example, we rely on the two things:
- The container that is used inside the factory is “prepared” and
initialized - so we can add new items.
*, In other words, the container must be initialized before we register the first type. - The initialization of
s_registered
is invoked, and the variable is not optimized.
Additionally, we don’t rely on the order of initializations between types. So if we have two classes like “Foo” and “Bar”, the order in which they end up in the factory container doesn’t matter.
I mentioned that the two points are satisfied by the following fact from the Standard:
variable with static storage duration has initialization or a destructor with side effects; it shall not be eliminated even if it appears to be unused.
Moreover, for static variables, Zero
initialization
is performed before Dynamic
initialization:
so the map will be initialized first - during Zero initialization, and
the s_registered
variables are then initialized in the Dynamic part.
But how about linkers and using such approach in static libraries.?
It appears that there are no explicit rules and our classes might not be registered at all!
Example
Let’s consider the following application:
The client app:
#include "CompressionMethod.h"
int main()
{
auto pMethod = CompressionMethodFactory::Create("ZIP");
assert(pMethod);
return 0;
}
The application just asks to create ZIP
method. The factory with all
the methods are declared and defined in a separate static library:
// declares the basic interface for the methods
// and the factory class:
CompressionMethod.h
CompressionMethod.cpp
// methods implementation:
Methods.h
Methods.cpp
Notice that in the client app we only include “CompressionMethod.h”.
The effect
In the register()
method I added simple logging, so we can see what
class is being registered. You could also set a breakpoint there.
I have two compression method implementations: “Zip” and “Bz”.
When all of the files are compiled into one project:
But when I run the above configuration with the static library I see a blank screen… and error:
The reason
So why is that happening?
The C++ standard isn’t explicit about the linking model of static libraries. And the linker usually tries to pull unresolved symbols from the library until everything is defined.
All of s_registered
variables are not needed for the client
application (the linker doesn’t include them in the “unresolved set” of
symbols), so they will be skipped, and no registration happens.
This linker behaviour might be a problem when you have a lot of self-registered classes. Some of them register from the static library and some from the client app. It might be tricky to notice that some of the types are not available! So be sure to have tests for such cases.
Solutions
Brute force - code
Just call the register methods.
This is a bit of a contradiction - as we wanted to have self-registering types. But in that circumstance, it will just work.
Brute force - linker
Include all the symbols in the client app.
The negative of this approach is that it will bloat the final exe size.
For MSVC
/WHOLEARCHIVE:CompressionMethodsLib.lib
- in the additional linker options.- MSDN documentation
- introduced in Visual Studio 2015 Update 2.
For GCC
-whole-archive
for LD
This option worked for me, but in the first place I got this:
While s_registered
variables are initialized, it seems that the map is
not. I haven’t investigated what’s going on there, but I realized that a
simple fix might work:
To be sure that the container is ready for the first addition we can wrap it into a static method with a static variable:
map<string, CompressionMethodInfo>&
CompressionMethodFactory::GetMap()
{
static map<string, CompressionMethodInfo> s_methods;
return s_methods;
}
And every time you want to access the container you have to call
GetMap()
. This will make sure the container is ready before the first
use.
“Use Library Dependency Inputs”, MSVC
- “Use Library Dependency Inputs” in the linker options for MSVC
Any more ideas?
Wrap up
Initialization of static variables is a tricky thing. Although we can be sure about the order of initialization across one application built from object files, it gets even trickier when you rely on symbols from a static library.
In this post, I’ve given a few found ideas how to solve the problem, but be sure to check what’s the best in your situation.
Once again thanks for the feedback on r/cpp
for my previous article.
The code for Visual Studio can be found here: fenbf/CompressFileUtil/factory_in_static_lib
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: