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.
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.