Static member variable not global between executab

2020-05-07 07:24发布

问题:

My knowledge is a bit fuzzy in terms of how linking a DLL works but I'm observing a change to a static member variable in an executable that doesn't change the same static member variable in a DLL. Here's the scenario I have:

main.cpp is statically linked to mylib.lib. Within mylib.lib, I have the following class:

// foo.h
class Foo
{
public:
    static int m_global;
    Foo();
    ~Foo();
};

and

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

int Foo::m_global = 5;

I also have a DLL that links to mylib.lib with the following:

//testdll.h
#define MATHLIBRARY_API __declspec(dllimport)

void MATHLIBRARY_API printFoo();

and

// testdll.cpp
#include "testdll.h"
#include <iostream>

void printFoo() {
    std::cout << Foo::m_global << std::endl;
}

Finally, in main.cpp of my executable

// main.cpp
#include <iostream>
#include "testdll.h"
#include "foo.h"

int main() {
    std::cout << Foo::m_global << std::endl;
    Foo::m_global = 7;

    std::cout << Foo::m_global << std::endl;
    printMutiply();

    return 0;
}

My expected output is 5, 7, 7. However, I'm seeing 5, 7, 5 which is telling me that the static member variable change isn't being seen by the DLL. Why is this so? And how can I make the DLL see changes in the static member variable made in the executable??

回答1:

Believe it or not, but your application violates One Definition Rule, and as such, triggers Undefined Behavior. Your program (as it is called in C++ standard) ends up having double definition of Foo::m_global - one in the loadable library, and another one inside main. As an observable effect of this undefined behavior, Microsoft dynamic loader creates two symbols, one from loadable object, another from main.

In Linux word, ld (linux loader) would actually conflate those symbols into one (which would, in turn, trigger double destruction for non-trivial objects).

Bottom line - do not share definitions of global symbols between loadable libraries and executable. This goes for both functions and variables, but sharing functions usually does not have a visible side-effect, although technically is undefined behavior as well.



回答2:

This is how we handle it in our projects:

/* First a general definition which covers differences
 * of Windows and Linux for all of your libraries:
 */
#ifdef _WIN32
/* for Windows, Visual C++ */
#define MY_EXPORT __declspec(dllexport)
#define MY_IMPORT __declspec(dllimport)
#else /* _WIN32 */
/* for gcc */
#define MY_EXPORT __attribute__((visibility("default")))
#define MY_IMPORT __attribute__((visibility("default")))
#endif /* _WIN32 */

This has to be prepared for each of your libraries:

/* The API macro to distiguish two cases:
 * 1. DLL/Shared Object
 * 2. usage of DLL/Shared Object
 */
#ifdef BUILD_MY_LIB
#define  MY_LIB_API MY_EXPORT
#else /* BUILD_MY_LIB */
#define MY_LIB_API MY_IMPORT
#endif /* BUILD_MY_LIB */

MY_LIB_API is then used in any class to be exported from MyLib library:

class MY_LIB_API Foo {
};

The rest is done with compiler arguments:

  1. To compile the MyLib DLL or Shared Object, -DBUILD_MY_LIB is added to command line arguments of compiler.

  2. To use the DLL or Shared Object, no additional setting is necessary.

Actually, our solution considers static libraries also. It defines MY_LIB_API to be empty. However, we didn't used it for a long time anymore. Hence, I left this part out. (I must admit I forgot how it works in detail...)



标签: c++ linker