4

As far as I understand namespace scope static variables should have one copy in each compilation unit. So if I have a header file like this:

class BadLad {                                                                                      
public:                                                                                             
    BadLad();                                                                                       
    ~BadLad();                                                                                      
};                                                                                                  

static std::unique_ptr<int> sCount;                                                                 
static BadLad sBadLad;

and badlad.cpp

#include "badlad.h"

BadLad::BadLad() {
    if (!sCount) {
        sCount.reset(new int(1));
        std::cout<<"BadLad, reset count, "<<*sCount<<std::endl;
    }
    else {
        ++*sCount;
        std::cout<<"BadLad, "<<*sCount<<std::endl;
    }
}

BadLad::~BadLad() {
    if (sCount && --*sCount == 0) {
        std::cout<<"~BadLad, delete "<<*sCount<<std::endl;
        delete(sCount.release());
    }
    else {
        std::cout<<"~BadLad, "<<*sCount<<std::endl;
    }
}

I expect sCount and sBadLad to be unique in each cpp file that includes badlad.h.

However, I found it's not the case in the following experiment:

  • I compile badlad to a shared library libBadLad.so.
  • I create another shared library libPlugin.so which links libBadLad.so, only plugin.cpp includes badlad.h, so I expect there is one copy of sCount in libPlugin.so.
  • I create a main program that links libBadLad.so, I expect there is one copy of sCount in main.

The main program looks like this:

#include <dlfcn.h>

int main() {
    void* dll1 = dlopen("./libplugin.so", RTLD_LAZY);
    dlclose(dll1);

    void* dll2 = dlopen("./libplugin.so", RTLD_LAZY);
    dlclose(dll2);

    return 0;
}

When executing the main program, I can see the sCount variable is first created and set to 1 before main is called, which is expected. But then after the first dlopen is called, sCount is incremented to 2, and subsequently decreased to 1 when dlclose is called. The same happens to the second dlopen/dlclose.

So my questions is, why there is only one copy of sCount? Why the linker doesn't keep the copies separate (which I think is what most people expect)? It behaves the same if I link libPlugin.so to main directly instead of dlopen.

I'm running this on macOS with clang-4 (clang-900.0.39.2).

EDIT: please see the full source code in this repo.

swang
  • 5,157
  • 5
  • 33
  • 55
  • You could take a thorough look at the [2nd post](https://stackoverflow.com/questions/2624880/static-class-variables-in-dynamic-library-and-main-program?rq=1) in the related section. I think the underlying mechanism is merely the same. –  Feb 05 '18 at 18:29
  • welcome to dll hell. Basically each dll will have the static/global variables liked with it's code alone. – Raxvan Feb 05 '18 at 18:34
  • @TheDude that post says there are indeed two copies, but the main one somehow masked the one from shared library? but why the linker prefers to do this, doesn't it go against common understanding of static variable? – swang Feb 05 '18 at 18:37
  • I believe it's a _thread safety_ thing, because shared libraries can be hosted in different thread contexts, and static variable instantiation is guaranteed to be thread safe. The linker doesn't know anything about the thread context though. I'm not sure about that, otherwise I'd post it as an answer. [`dlopen()`](http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html) is a POSIX standard BTW. –  Feb 05 '18 at 18:42
  • One idea that comes to mind, which could work the way you expect to have separate copies for separate translation units, is to put the `static` variable definitions into an anonymous namespace in those TUs. These should be really private copies within there. –  Feb 05 '18 at 18:57
  • @TheDude I tried that, by putting sCount and sBadLad in a `namespace {}`, but it doesn't work either, there is still one copy. The other post you linked suggested to use version script, I haven't tried it but it feels wrong, it means my build script will need to know my library's implementation details. – swang Feb 06 '18 at 08:20
  • If your main program links libBadLad.so, then there's only one copy of libBadLad.so during the execution. libPlugin.so doesn't load its own copy. – n. m. could be an AI Feb 06 '18 at 11:17
  • Is that because the symbols have already been resolved at start up time? Then why is BadLad constructor called after dlopen? – swang Feb 06 '18 at 19:25
  • It isn't on Linux or on Windows/Cygwin. Show your build commands, print the address of the object being constructed/destructed (just `<< this`), and show your output. – n. m. could be an AI Feb 07 '18 at 07:41
  • Your executable uses three (two at a time) different instances of sBadLad, but only one instance of sCount. Why is that is an interesting question that I don't have time to answer right now, maybe later. – n. m. could be an AI Feb 07 '18 at 10:09
  • I have scribbled a new answer that seems to describe your situation adequately. – n. m. could be an AI Feb 07 '18 at 11:29

1 Answers1

3

(Iteration 2)

What happens in your case is very interesting and very unfortunate. Let's analyze it step by step.

  1. Your program is linked against libBadLad.so. This shared library is thus loaded on program startup. Constructors of static objects are executed before main.
  2. Your program then dlopens libplugin.so. This shared library is then loaded, and constructors of static objects are executed.
  3. What about libBadLad.so that libplugin.so is linked against? Since the process already contains an image of libBadLad.so, this shared library is not loaded the second time. libplugin.so could just as well not link against it at all.
  4. Back to the static objects of libplugin.so. There are two of them, sCount and sBadLad. Both are constructed, in order.
  5. sBadLad has a user-defined non-inline constructor. It is not defined in libplugin.so, so it is resolved against the already-loaded libBadLad.so, which has this symbol defined.
  6. BadLad::BadLad from libBadLad.so is called.
  7. This constructor references a static variable sCount. This resolves to sCount from libBadLad.so, not sCount from libplugin.so, because the function itself is in libBadLad.so. This is already initialised and is pointing to an int that has the value of 1.
  8. The count is incremented.
  9. Meanwhile, sCount from libplugin.so sits quietly, being initialised to nullptr.
  10. The library is unloaded and loaded again, etc.

And the moral of the story is? Static variables are evil. Avoid.

Note the C++ standard has nothing to say about any of this, as it does not deal with dynamic loading.

However a similar effect can be reproduced without any dynaamic loaading.

   // foo.cpp
   #include "badlad.h"

   // bar.cpp
   #include "badlad.h"
   int main () {}

Build and test:

   # > g++ -o test foo.cpp bar.cpp badlad.cpp
   ./test
   BadLad, reset count to, 1
   BadLad, 2
   BadLad, 3
   ~BadLad, 2
   Segmentation fault

Why segmentation fault? This is our good old static initialisation order fiasco. The moral of the story? Static variables are evil.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • You are totally right! If I make BadLad constructor inline, sCount will never surpass 1. – swang Feb 07 '18 at 11:46