4

Please consider following peace of code:

// 1. Single header file. Imagine that it is some static library.
// Counter.h
#pragma once

struct Counter
{
    Counter()
    { 
        ++getCount();
    }

    static int& getCount()
    {
        static int counter = 0;
        return counter;
    }
};

// 2. Shared library (!) : 
// main_DLL.cpp
#include <iostream>
#include "counter.h"

extern "C"
{
   __declspec(dllexport) // for WIN
   void main_DLL()  
   {
       Counter c;
       std::cout << "main_DLL : ptr = " << &Counter::getCount()<< " value = " << Counter::getCount() << std::endl;
   }
}

// 3. Executable. Shared library statically (!) linked to the executable file.
// main.cpp

#include "counter.h"
#include <iostream>

extern "C"
{
    __declspec(dllimport) // for WIN
    void main_DLL();
}

int main()
{
    main_DLL();
    Counter c;
    std::cout << "main_EXE : ptr = " << &Counter::getCount() << " value = " << Counter::getCount() << std::endl;
}

Results:

Results for WIN (Win8.1 gcc 5.1.0):
main_DLL : ptr = 0x68783030 value = 1
main_EXE : ptr = 0x403080 value = 1
// conclusion: two different counters

Results for UNIX (Red Hat <I don’t remember version exactly> gcc 4.8.3):
main_DLL : ptr = 0x75693214 value = 1
main_EXE : ptr = 0x75693214 value = 2
// conclusion: the same counter addressed

Building:

Building for WIN:
g++ -c -Wall -Werror -o main_DLL.o main_DLL.cpp
g++ -shared -Wl,--out-implib=libsharedLib.a -o libsharedLib.so main_DLL.o
g++ -Wall –Werror -o simpleExample main.cpp -L./ -lsharedLib

Building for UNIX:
g++ -c -Wall -Werror -fPIC -o main_DLL.o main_DLL.cpp
g++ -shared -fPIC -o libsharedLib.so main_DLL.o
g++ -Wall –Werror -fPIC -o simpleExample main.cpp -L./ -lsharedLib

So, you see that I added –fPIC on UNIX and there is no need to create import library for UNIX, because all exports symbols are included inside shared library. On Windows I use __declspec for it.

For me, results on Windows are pretty much expected. Because shared library and executable are building separately and they should know about static variable in Counter::getCount. They should simply allocate memory for it, that’s why they have different static counters.

I did quite some analysis using tools like nm, objdump. Although I’m not a big expert in them, so I haven’t found anything suspicious. I can provide their output if needed.

Using ldd tool I can see that library linked statically in both cases.

Why I can’t see the same results on Unix for me it’s strange. Could the root cause lie in building options (–fPIC, for example), or I’m missing something?

Denis Bakhvalov
  • 313
  • 2
  • 9
  • Unix does not have `DLLs`. – Ed Heal Jul 18 '15 at 21:46
  • note that the [correct result](http://stackoverflow.com/questions/6223355/static-variables-in-class-methods) is that there should only be one instance of the static variable. – M.M Aug 04 '15 at 00:15
  • @MattMcNabb, what you mean by correct result? Note, that in the example there are shared library and executable. And while building, they do not know about each other. – Denis Bakhvalov Aug 04 '15 at 22:26
  • @DenisBakhvalov "correct result" means what the C++ standard says. Windows DLLs are weird, "static" data in a DLL seems to mean "local data to this instance of LoadLibrary". If you unload and reload the DLL you get refreshed static data. – M.M Aug 04 '15 at 22:40
  • @MattMcNabb, then you'd better use the word "expected" result. Because correct result is the output that we received on particular machine with particular peace of code. – Denis Bakhvalov Aug 06 '15 at 06:26

2 Answers2

4

In windows, A DLL is not exporting global and static symbols unless you add the dllexport statement, therefore, the linker doesn't even know they exists, so it allocate new instance for the static member.

In linux/unix a shared lib is exporting all the global and static symbols, so when the linker find the existence of the static member in the shared lib, it just use its address.

That is the reason for the different result.

SHR
  • 7,940
  • 9
  • 38
  • 57
  • Thanks! Do you know how I can cofirm it using tools like nm and objdump? – Denis Bakhvalov Jul 18 '15 at 23:00
  • 1
    using `nm` in windows you'll see the static variable twice, once in the DLL and the second in the executable, they'll have a different addresses, and both will be shown as internal (upper case letter). in Linux/Unix you'll see it as external in the executable (lower case) and as internal in the shared lib (upper case). – SHR Jul 19 '15 at 07:16
  • I managed to fix this problem on Windows. To do that I break the link between implementation of Counter and executable. I.e. I moved implementation of Counter in cpp file and include it in dll. Counter is exported from dll. Executable includes only Counter header. In such situation there is one instance of static variable inside dll. Output: main_DLL : ptr = 0x68787020 value = 1 main_EXE : ptr = 0x68787020 value = 2 But I can't change the behaviour on Linux. What should I do, if I want to have two different static variables in both shared library and executable? – Denis Bakhvalov Jul 20 '15 at 21:06
  • To do so you'll need to declare 2 classes, for example, it can be done by a macro in the lib's source file that rename the class to something else. like: put in the main_DLL.cpp `#define Counter CounterDll` before the `#include "counter.h"` from now in the main_DLL.cpp you'll use the renamed class. – SHR Jul 21 '15 at 20:25
  • Yeah, it works. Thank you very much for your answers! – Denis Bakhvalov Jul 23 '15 at 06:59
1

EDIT: This is a complete rewrite of the answer. With much more details.


I think that this question deserves more elaborated answer. Especially that there are things that were not mentioned so far.

Dependency Walker

Let me start with referring to the “Dependency Walker” program.

It is a nice program (although these days a bit old-schoolish in its look & feel) that allows analyzing Windows binaries (both EXE and DLL) for symbols that they export/import and their own dependencies to other DLLs. Also it allows showing undecorated symbol names but this seems to be working only with MSVC build binaries. (And some more but that is not important here.)

Thanks to this program crucial information (for this question) can be uncovered. So I encourage you to use it during experiments.

Exporting policy on Linux vs. Windows

SHR already pointed this out but I will mention it also for completeness of the answer. And some extra details.

On Linux every symbol gets exported from a shared library by default. On the other hand on Windows you have to explicitly state which symbols to export from a shared library.

GCC seems however to provide some means of controlling exports in "Windows style". See for example Visibility entry on GCC Wiki.

Also note that there are various ways of exporting on both Linux and Windows. For example both seem to support exporting selectively by providing linker with a list of names for symbols to export. But it also seems that nowadays (on Windows at least) this isn't really used much. __declspec approach seems to be preferred.

What can be exported?

After that general introduction let's now stick to Windows case. Nowadays you export/import symbols from shared libraries by using the __declspec. Just as shown in the question. (Well maybe not exactly that - typically you use a #define to handle bi-directionality as shown in already mentioned Visibility entry on GCC Wiki.)

But the declaration can be applied not only to functions, methods and global variables. It can also be applied to types. For example you can have:

class __declspec(dllexport) Counter { /* ... */ };

Such exporting/importing means in general that all members get exported/imported.

Not so easy!

But it would be too easy, wouldn't it? The complication is that GCC and MSVC handle exporting types differently.

My notes here are based mostly on experiments (checks done using Dependency Walker) so I can be wrong or not precise enough. But I did observe differences in behavior.

In tests I used MSVC 2013 from the Express Edition with update 5. For GCC I used MinGW distro from nuwen.net, version 13.0.

MSVC, when exporting entire type, exports each and every member. Including implicitly defined members (like compiler generated copy constructor). And including inlined functions. Furthermore if inlined function has some static local variables they get exported to (!).

GCC on the other hand seems to be far more restrictive. It doesn't export implicitly defined members. Nor it doesn't export inlined members.

Exporting/Importing inline functions

If instead of exporting entire type you would explicitly export an inlined function then and only then will GCC really export it. But still it will not export static local variables in that function.

Further more if you try to import an inlined function GCC will error. With GCC you cannot define symbols that you are importing. And this happens when you import inlined (and so defined) symbol. So in fact it doesn't make any sense to export inlined functions with GCC.

MSVC allows to import inlined functions. In all cases I checked it didn't seem to actually inline the function but instead called the imported version.

Yet note that because MSVC in case of inlined function exports also its static local variables it would be possible for it to really inline the function (rather than import it) while maintaining a single copy of static local variables. For ordinary programs such behavior is mandated by the Standard (N3337, C++11), in point 7.1.2 ([dcl.fct.spec]) at $4 we can read:

(…) A static local variable in an extern inline function always refers to the same object. (…)

But a program and a shared library are actually more like two programs so they are out of scope for the Standard. Yet MSVC even in that case acts (or better to say: could act) as one would expect from a single program.

Solution

Denis Bakhvalov in a comment provided solution for his own question. The solution is to move getCount function from header to source file and export/import it.

This seems to be the only solution portable between GCC and MSVC. Or to be more precise MSVC allows more solutions to this problem but none of them will work when program is build under GCC.

The variable trick

The above is not entirely true. There is another workaround that will work consistently between GCC and MSVC.

This is to stop using static local variable. Instead make it a global variable (most likely by making it static variable in the class) and export it. This will make the trick as well.

Sadly there is no way (or I don't know any) to directly force exporting/importing static local variables. You have to change them to global variables to do that.

MSVC solutions

With MSVC you have more options.

As mentioned before exporting/importing the inlined function itself (whether directly or through type) will do the job.

Summary

As described above even consistency between GCC and MSVC on Windows only requires care. You have to limit yourself to stay in common subset of allowed solutions.

Keeping the program (source) interoperable between Linux and Windows even if with same compiler (GCC) also requires care.

Luckily there is a common subset for all three environments: GCC on Linux, GCC on Windows and MSVC on Windows. That common subset is described already by mentioned Denis' comment.

So do not inline functions that you intend to export/import. Keep them in sources. And on Windows builds (regardless of compiler) export them explicitly (otherwise you will get linker error anyway since the functions in sources of a shared library will not be available when building program).

Note that this is actually a reasonable approach on its own. Inlining function from shared library doesn't seem wise. It freezes not only the interface but also implementation (of that function). You can no longer change this function freely (and deliver new version of your shared library) since all clients would have to be rebuild since they could have inlined that function. So it is a wise approach by itself not to inline from shared library. And as a bonus it assures that your sources are multi-platform friendly.

Also do have a look into the mentioned Visibility entry on GCC Wiki. It might be reasonable to use that approach (of explicit exports) on Linux as well since it seems cleaner (from design point of view) and more efficient at runtime. While it fits well what you have to do for Windows anyway.

Community
  • 1
  • 1
Adam Badura
  • 5,069
  • 1
  • 35
  • 70