0

First of all problem context: Linux x64, gcc v4.8.5. There is an application which loads two shared libraries (let it be module1.so and module2.so), these modules has partially the same code. Now a little bit of code:

//SomeClass.h
class SomeClass
{
public:
    static unsigned long& s_uObj2()
    {
        static unsigned long s_uObj2;
        return s_uObj2;
    };
    void Initialize();
};

//SomeClass.cpp
void SomeClass::Initialize()
{
     if (0 == s_uObj2())
     {
         //do init
     }
     s_uObj2()++; //acts as a counter
}

This code have been written long time ago and it's idea is to prevent double initialization of SomeClass in each module. The problem: this implementation somehow shares s_uObj2 value across different modules (in single application), which leads to the fact that only first module will be initialized.

How is that possible? I thought it should be isolated address space between different modules?

Please don't point me to some general case definition of "how static variables work". What I really need - is analysis of why different modules share the value of single variable in this exact case. That is because it is real project and I'm unable o refactor it all to make it work.

  • Related? [Link](http://stackoverflow.com/a/19374253/7359094). – François Andrieux Mar 03 '17 at 15:17
  • @FrançoisAndrieux I didn't see anything helpful in this post - it says "In all cases, static global variables (or functions) are never visible from outside a module (dll/so or executable)". And in my case I have DIFFERENT global variables in DIFFERENT modules. –  Mar 03 '17 at 15:23
  • In your case you have no static global variables, you have a static *local* variable, which is in many ways like a *non-static* global variable. Read the part about `extern` globals in Linux. – François Andrieux Mar 03 '17 at 15:29
  • @FrançoisAndrieux, thanks of course, but actually you are not helping at all by pointing to some other general case –  Mar 03 '17 at 15:36
  • Are you saying that `SomeClass` exists in both libraries? The question is a little unclear. And dynamic libraries do *not* get loaded into separate address spaces, they get loaded into different pieces of the app's address space. – Mark Ransom Mar 03 '17 at 16:03

1 Answers1

1

The problem: this implementation somehow shares s_uObj2 value across different modules (in single application), which leads to the fact that only first module will be initialized.

How is that possible? I thought it should be isolated address space between different modules?

That's the One Definition Rule requirement of C++ standard. The rule basically says that all global object with same name must resolve to a single definition throughout the program. For example all global variables (including class statics like in your case) must resolve to the same single object.

Now GCC tries really hard to preserve ODR across all shared libraries. If you build above code and examine it's exported symbols, you can see that it exports SomeClass::s_uObj2:

$ g++ tmp.cpp -shared -fPIC && objdump -T a.out | c++filt
...
0000000000200970  w   DO .bss   0000000000000008  Base        SomeClass::s_uObj2()::s_uObj2

This means that at startup dynamic linker will resolve all duplicate copies of SomeClass::s_uObj2()::s_uObj2 to a single object (which is a copy in first shared library that happens to be loaded).

The usual way to overcome this issue (if you are really willing to give up on ODR which is bad bad) is to avoid exporting s_uObj2 from the library i.e. limiting it's visibility.

There are many ways to do this, I'll just name a few:

  • compile with -fvisibility-inlines-hidden

  • attach __attribute__((visibility("hidden"))) to s_uObj2's definition

  • put your class declaration inside

    #pragma GCC visibility push(hidden)

    ...

    #pragma GCC visibility pop

The first one is nasty because it'll effectively disable ODR for all of your code, not just the snippet above. The latter two are more fine-grained.

Community
  • 1
  • 1
yugr
  • 19,769
  • 3
  • 51
  • 96
  • It appears gcc/Linux works harder at this than Visual Studio/Windows. I had a problem years ago with multiple singletons because different DLLs had their own copy of a local static variable. – Mark Ransom Mar 03 '17 at 17:40
  • That's it! Thanks. Can you explain why is this happens only in Linux? I've tested this code in Windows (Visual Studio 2010) and Android NDK - there were no such problems. –  Mar 03 '17 at 17:58
  • And actually it seems to be the proper GCC argument is "-fvisibility=hidden" –  Mar 03 '17 at 18:10
  • @AlekDepler `-fvisibility=hidden` is even more dangerous than `-fvisibility-inlines-hidden` which I didn't recommend. I will hide _all_ symbols in your library (thus making it useless unless you then specifically annotate exported functions). Rebuilding your SW with default hidden visibility is a _huge_ change and shouldn't be taken lightly. – yugr Mar 03 '17 at 21:13
  • @MarkRansom Indeed but this comes with a cost - GCC isn't allowed optimize functions in shared libraries as good as Visual Studio can. – yugr Mar 03 '17 at 21:14