Partners: KDAB Whole Tomato Software CppDepend

12 February 2018

Static Variables Initialization in a Static Library, Example

C++ static variables initialization and linker, one example

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:

  1. 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.
  2. 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:

Properly registered static variables

But when I run the above configuration with the static library I see a blank screen… and error:

static variables not initialized

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.

For GCC

  • -whole-archive for LD

This option worked for me, but in the first place I got this:

Factory types, almost registered

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

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

Get my free ebook about C++17!

More than 50 pages about the new Language Standard.

C++17 in detail, by Bartlomiej Filipek

For now I don't have my own courses, but I promote others :) (Please note, I'll also get a little commission for every signup. That's a huge support for my work!). Have a look my recommended C++ courses at @Pluralsight (more info in my Resource page):

© 2017, Bartlomiej Filipek, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.