Good Friday everyone!
Today I want to talk about some insidious features of static variables with improper linking of executable modules. I will show the problem from my real practice, which may arise for everyone.
I chew on everything in some detail, so the "experienced" and red-eyed people may have the feeling that I am "crashing into the sandbox", but this article is not only for them.
Let's imagine a situation: there is some class implemented in a static library (lib). This library statically binds the implementation module (dll). Further this dll also statically binds the executable (exe). In addition, the Exe module statically links the static library (lib).
Like that:

')
For example, here is the following logic: in lib, there is some tool implemented for something. In dll'e implemented some functionality based on this tool. In exe implemented a test for this functionality. Dll itself does not export the tool class (which is in lib'e), so the test requires static linking of libs.
Let the instrumental class contain a static variable. And in dll there is a function for creating this class, and the object is returned by value.
Here is an augmented diagram:

Here is the C ++ code:
- lib
ListAndIter.h#pragma once #include <list> using namespace std; class ListAndIter { private: std::list<int>::iterator iter; static std::list<int> &getList(); public: void foo(); ListAndIter(); ListAndIter(ListAndIter& rhs); ~ListAndIter(); };
ListAndIter.cpp #include "ListAndIter.h" ListAndIter::ListAndIter() { getList().push_front(0); iter = getList().begin(); } ListAndIter::ListAndIter(ListAndIter& rhs) { this->iter = rhs.iter; rhs.iter = getList().end(); } std::list<int> & ListAndIter::getList() { static std::list<int> MyList; return MyList; } ListAndIter::~ListAndIter() { if (iter != getList().end()) getList().erase(iter); } void ListAndIter::foo() { }
- dll
GetStaticObj.h #pragma once #include "ListAndIter.h" #ifdef _DLL_EXPORTS #define _DLL_EXP __declspec(dllexport) #else #define _DLL_EXP __declspec(dllimport) #endif _DLL_EXP ListAndIter GetStaticObj();
GetStaticObj.cpp #include "GetStaticObj.h" ListAndIter GetStaticObj() { ListAndIter obj; obj.foo(); return obj; }
- exe
Main.cpp #include "GetStaticObj.h" int main() { ListAndIter obj = GetStaticObj(); obj.foo(); }
As you can see from the code, there is a special function foo that serves to bypass the
RVO so that the copy constructor is called. Let me remind you that both the dll module and the exe module are assembled independently of each other, so they should be aware of the existence of a static variable in the lib and therefore create them in themselves.
The object of the ListAndIter class is returned through the copy constructor, so when an object is received on the exe-module side, all references to a static variable will become invalid. Steps it looks like this:
- * .exe: GetStaticObj () function call.
- Dll.dll: creating a temporary object of the ListAndIter class. A zero is put in the list, the iter iterator points to it. Moreover, at this time the static variable on the exe-module side is empty, respectively, the iterator is not valid.
- Dll.dll: The copy constructor for the object of the ListAndIter class is called. In the temporary object, the iterator was not valid. In a new object, an iterator points to a list of DLL.dll, although the object itself is created on the side of the exe module.
- Dll.dll: Destroys a temporary object of the ListAndIter class. Since the iterator is not valid no action occurs.
- * .exe: The destructor for the obj object is called. When trying to compare an iterator with getList (). End (), a Windows error pops up: "Iterators are not compatible." That is, an iterator from the "other list".
Let's try to correct this situation by removing the dependence of the exe-module on the static library. Then all the functionality of the static library must be exported via dll (see the code below):

Code changes:
- Created a new header file, shared.h. It describes export macros. Put the file in the lib:
shared.h #pragma once #ifdef _DLL_EXPORTS #define _DLL_EXP __declspec(dllexport) #else #define _DLL_EXP __declspec(dllimport) #endif
- ListAndIter.h added export directives:
ListAndIter.h #pragma once #include <list> #include "shared.h" using namespace std; class ListAndIter { private: std::list<int>::iterator iter; _DLL_EXP static std::list<int> &getList(); public: _DLL_EXP void foo(); _DLL_EXP ListAndIter(); _DLL_EXP ListAndIter(ListAndIter& rhs); _DLL_EXP ~ListAndIter(); };
- In the dll, respectively, removed the macro export ads:
GetStaticObj.h #pragma once #include "ListAndIter.h" #include "shared.h" _DLL_EXP ListAndIter GetStaticObj();
Now the object will be created and deleted only on the dll side. There will be no static variable in the exe-module and such code will work successfully.
Now let's assume what will happen if the ListAndIter class has become template:

For each full specialization of the template and all objects of such classes there must be its own static variable.
First, we are obliged to place the template class implementation in the header file, since patterns are expanded at compile time.
If a static variable is a member of a class, then in order to successfully build our project, we have to explicitly initialize these variables in all the modules used. In this case, we explicitly create two static variables, which brings us back to the 1st example.
Otherwise, if a static variable is not a member of the class, but is created through a static method, then in this case it is also created, but already implicitly for us. Error repeats again.
To resolve this situation, you need to create an intermediate lib, in which to place this functionality. That is, instead of dll do lib. Then again there will be one static variable.
Conclusion : When using static variables in static libraries, care must be taken to ensure that executable modules do not link statically to each other.
Sometimes the problem is not solved by simplifying dependencies. For example, a class is implemented in a static library and it has a certain static counter of instances. This static library is linked into two different dlls, so two different counters are created in them. In this case, the problem is solved by converting the static library into a dynamic library (dll). Accordingly, the other two dll link the new dll dynamically. Then the static variable will be only in one dll (in the one to which the class with the counter is implemented).
All code can be taken from
github .
PS I wrote a lot of things, maybe not perfect ... I will be happy with the advice and comments.