7

I am confused by the result i am getting from this code. In one dll the counter is incremented when the static variable is initialized. Then when main is executed i read this counter but i get 0 instead of 1. Can anybody explain this to me?

In my dynamic library project:

// Header file
class Foo {
   int i_ = 0;

   Foo(const Foo&) = delete;
   Foo& operator= (Foo) = delete;

   Foo()
   {
   }

public:
   void inc()
   {
      ++i_;
   }

   int geti()
   {
      return i_;
   }

   static Foo& get()
   {
      static Foo instance_;
      return instance_;
   }

   Foo( Foo&&) = default;
   Foo& operator= (Foo&&) = default;
};

int initialize()
{
   Foo::get().inc();
   return 10;
}

class Bar
{
   static int b_;

};

// cpp file
#include "ClassLocalStatic.h"


int Bar::b_ = initialize();

In my application project

// main.cpp
#include <iostream>

#include "ClassLocalstatic.h"

int main(int argc, const char * argv[])
{
   std::cout << Foo::get().geti();
   return 0;
}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
monamimani
  • 273
  • 2
  • 11
  • 1
    Try looking up Static order initialization fiasco – Antimony Aug 15 '12 at 01:19
  • I just tried this and it gives me 1. Maybe you should not depend on static initialization order... – Henry Hu Aug 15 '12 at 01:23
  • @Antimony I don't think this is the case. Because the order in defined in this cas... When Bar::b_ get initialized it will instantiate Foo and increment the counter. This will happen before main. Which it does, but the problem is that in main Foo::get() actually create a new Foo, different then the one created in initialize(). That is what i don't understand. Also take note that if all this code is the same library the output is 1. – monamimani Aug 15 '12 at 01:26
  • @Henry Hu i don't depend on the initialization order. it is defined in this case. Are you sure you tested with the code in a lib and main in another project? I am maybe suspecting a bug in Xcode, but I really doubt it... – monamimani Aug 15 '12 at 01:27
  • @monamimani I compiled the first .h and .cpp into a .so, and then compiled the second into an executable linked with the library. – Henry Hu Aug 15 '12 at 01:29
  • Can you give more details about the loading of the code? Are you loading a shared library at runtime? – Kerrek SB Aug 15 '12 at 01:29
  • Does http://stackoverflow.com/questions/5114683/loading-dll-not-initializing-static-c-classes help at all? Sounds very pertinent to your problem. – Rollie Aug 15 '12 at 01:30
  • @ Kerrek SB, no i am not loading the the code at run time. This is the whole real code to replicate this behavior. My application which only contain main.cpp link on the dynamic lib and thats it. – monamimani Aug 15 '12 at 01:55
  • @Rollie, but I am not loading the dll at runtime. :( – monamimani Aug 15 '12 at 01:56
  • I have a hunch: Are you linking *object* files, or are you building an intermediate library? if `Bar::b_` is never needed by any other code, then it *will* be linked in from an object file, but it will *not* be linked in from a library file by default. – Kerrek SB Aug 15 '12 at 10:26

2 Answers2

10

The executable and DLL are both going to get their own copy of Foo::get(), each with their own copy of the static variable. Because they're in separate linker outputs, the linker can't consolidate them as it would normally.

To expand on this further:

The C++ specification allows an inline function to be defined in multiple translation units as long as they all have the same body; putting the function in a header file is perfectly OK because it ensures each copy will be identical. See https://stackoverflow.com/a/4193698/5987 . If there are static variables within the inline function, the compiler and linker need to work together to make sure that only one copy gets used between all of them. I'm not sure of the exact mechanics but it does not matter, the standard requires it. Unfortunately the reach of the linker stops after it has produced the output executable or DLL and it is unable to tell that the function exists in both places.

The fix is to move the body of Foo::get() out of the header and put it in a source file that's only in the DLL.

Community
  • 1
  • 1
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • But that doesn't make sens. if you have a singleton in one lib and you change its state from the same lib then the other lib wouldn't see those new state. – monamimani Aug 15 '12 at 01:53
  • @monamimani: The problem is that your functions are *inlined*. So each executable has their own separate copy. You need to take the function definitions out of the header. – Nicol Bolas Aug 15 '12 at 01:59
  • If this answer is correct, then why does the linker/loader not complaint about a clash? – Walter Aug 15 '12 at 08:12
  • @NicolBolas Really? Let say i use a singleton that declare it's instance as a local static variable, that mean that each call to getInstance() would get a fresh copy of the singleton. I doubt this is the case. – monamimani Aug 15 '12 at 10:12
  • @Walter, inline functions are explicitly allowed to have multiple definitions as long as they're identical. See my update. – Mark Ransom Aug 15 '12 at 16:21
  • @monamimani, a static variable usually gets shared between all copies of an inline function but a DLL is a special case. – Mark Ransom Aug 15 '12 at 16:23
  • The linker *is* usually able to do this, but you have to not disable this machinery. Under linux, provided you compile these classes with default visibility, I don't think you will run into the problems the questioner reports. The problems occur if the class is compiled with hidden visibility (which may be the default in his build config). I'm not familiar with windows, but I believe that they can achieve the same using `declspec(dllimport)` and/or some use of `declspec(dllexport)`. – Johannes Schaub - litb Aug 15 '12 at 19:43
  • So I cannot imagine that you can't use an inline function like this in a DLL file, with the proper flags. Can you please provide links that explain further what you say here? – Johannes Schaub - litb Aug 15 '12 at 19:45
  • @JohannesSchaub-litb I am actually compiling this on OsX with clang, with the default setting which are GCC_SYMBOLS_PRIVATE_EXTERN = NO and GCC_INLINES_ARE_PRIVATE_EXTERN = Yes what would be the proper flag? – monamimani Aug 15 '12 at 20:22
  • @MarkRansom But I want to have it in the header. Isn't there a way? What about the dllimport/export on windows or symbol visibility on osx? wouldn't it make it work? – monamimani Aug 15 '12 at 20:25
  • @MarkRansom If i put the function Foo::get() in a source file then it wont get inlined in the dll. I guess it is the only way... – monamimani Aug 17 '12 at 01:20
  • @monamimani, I suspect that a function containing a static variable will never be inlined, even if you declare it so. Remember that `inline` is only a hint to the compiler. – Mark Ransom Aug 17 '12 at 02:42
10

The rules of C++ state that inline function definitions will work correctly with static local variables. That is, if you inline function definitions, any local static variables will all be referring to the same variable.

However, there is one thing C++ does not define: DLLs.

The C++ specification is completely ignorant of DLLs; it has no idea how to handle them. C++ is defined in terms of static linkage, not dynamic.

As such, this means that the specification no longer applies when dealing with DLL boundaries. And that's where your problem comes from.

While C++ requires that inline functions with local static variables still work, C++ having no knowledge of DLLs means that everything is up to what the compiler decides to do.

It is perfectly legitimate compiler behavior for inline functions split across DLL boundaries to not have local static variables work as expected. It's such an outlier case that I seriously doubt any compiler developers spent time coding for such an eventuality.

The most reasonable thing for a compiler to do is exactly what it would do if you declared a extern global variable in a DLL header: each DLL and executable gets a separate one. That's why you need special syntax to say that a definition should be defined a by this executable/DLL (__declspec(dllexport)) and what will come from some other executable/DLL (__declspec(dllimport)).

You must always be careful of what you put across DLL boundaries. In general, don't inline things across DLL boundaries like this.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Is this behavior really compliant or merely reasonable? The C++ Standard doesn't address DLLs, but that doesn't mean DLLs are a blank check. If the Standard says static locals work for inline functions, a compliant implementation must abide. – Praxeolitic Aug 12 '14 at 13:09