To my surprise, when compiling two separate dylibs that happen to share a static library, the global variables defined in that static library seem to be shared. This SO article would seem to indicate that every dynamic library will keep its global variables separate, but in the case described above, test code included below proves that this is not the true. I am seeking confirmation that this is to be expected on macOS (and probably Linux as well).
Given this scenario:
- static lib "Foo" has a global called "Bar"; this is an int initialized to 123.
- dylib "AAA" links to "Foo"
- dylib "BBB" links to "Foo"
- application "MyApp" links to dylibs AAA and BBB.
- application calls function in dylib "AAA" that modifies Bar, setting it to 111
- application calls function in dylib "AAA" that prints Bar
- application calls function in dylib "BBB" that prints Bar
I expected that when "AAA" printed "Bar" it would be 111, and that when "BBB" printed bar it would still be 123. Instead, when "BBB" prints "Bar", it is 111, indicating that – from MyApp's point of view – there is only a single, shared instance of "Bar".
My suspicion is that since "Bar" is exposed by both "AAA" and "BBB", when you dynamically link the two dylibs, one of the two "wins" because the name is exactly the same and the linker can't distinguish the two.
This suspicion seems to be proved by setting the "-fvisibility=hidden" flag in the "Other C++ Flags" in Xcode. If I do this for the dylibs "AAA" and "BBB", then the two global variables seem to be distinct. I expect this is because 'visibility=hidden' hides the two copies of "Bar", thus resolving the conflict described in the paragraph above.
Can someone confirm my understanding of this?
--- SAMPLE CODE ---
The static library CGlobalTest has a C++ class as shown below. The class declares a global inside a function, a class global, and static global in the .cpp file. The function GetGlobal() returns a reference to one of these based on the GlobalType parameter.
CGlobalTest.cpp:
class CGlobalTest
{
public:
CGlobalTest() { }
static int& GetFunctionGlobal()
{
static int sFunctionGlobal = 123;
return sFunctionGlobal;
}
static int& GetClassGlobal()
{
return sClassGlobal;
}
static int& GetFileGlobal();
static int& GetGlobal(
GlobalType inType)
{
switch (inType) {
case kFunctionGlobal:
return GetFunctionGlobal();
break;
case kClassGlobal:
return GetClassGlobal();
break;
case kFileGlobal:
return GetFileGlobal();
break;
}
}
static int sClassGlobal;
};
CGlobalTest.h
#include "static_lib.h"
int CGlobalTest::sClassGlobal = 456;
int sFileGlobal = 789;
int&
CGlobalTest::GetFileGlobal()
{
return sFileGlobal;
}
I've then got two dynamic libraries that use the CGlobalTest static library, called global_test_dynamic_1 and global_test_dynamic_2. The code for 1 and 2 are essentially the same, so I'm including just the first one.
dynamic_lib_1.cpp:
#include "dynamic_lib_1.h"
#include "static_lib.h"
#include "stdio.h"
const char*
GlobalTypeToString(
GlobalType inType)
{
const char* type = "";
switch (inType) {
case kFunctionGlobal:
type = "Function Global";
break;
case kClassGlobal:
type = "Class Global";
break;
case kFileGlobal:
type = "File Global";
break;
}
return type;
}
void dynamic_lib_1_set_global(enum GlobalType inType, int value)
{
int& global = CGlobalTest::GetGlobal((GlobalType) inType);
global = value;
printf("Dynamic Lib 1: Set %s: %d (%p)\n", GlobalTypeToString(inType), global, &global);
}
void dynamic_lib_1_print_global(enum GlobalType inType)
{
const int& global = CGlobalTest::GetGlobal((GlobalType) inType);
printf("Dynamic Lib 1: %s = %d (%p)\n", GlobalTypeToString(inType), global, &global);
}
dynamic_lib_1.h
#ifdef __cplusplus
#define EXPORT extern "C" __attribute__((visibility("default")))
#else
#define EXPORT
#endif
#include "global_type.h"
EXPORT void dynamic_lib_1_set_global(enum GlobalType inType, int value);
EXPORT void dynamic_lib_1_print_global(enum GlobalType inType);
Finally, there is an application that links to the two dylibs.
#include "dynamic_lib_1.h"
#include "dynamic_lib_2.h"
#include "global_type.h"
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
typedef void (*print_func)(enum GlobalType inType);
typedef void (*set_func)(enum GlobalType inType, int value);
int main()
{
printf("App is starting up...\n");
// LOAD DYNAMIC LIBRARY 1
void* handle1 = dlopen("libglobal_test_dynamic_1.dylib", RTLD_NOW);
assert(handle1 != NULL);
print_func d1_print = (print_func) dlsym(handle1, "dynamic_lib_1_print_global");
assert(d1_print != NULL);
set_func d1_set = (set_func) dlsym(handle1, "dynamic_lib_1_set_global");
assert(d1_set != NULL);
// LOAD DYNAMIC LIBRARY 2
void* handle2 = dlopen("libglobal_test_dynamic_2.dylib", RTLD_NOW);
assert(handle1 != NULL);
print_func d2_print = (print_func) dlsym(handle2, "dynamic_lib_2_print_global");
assert(d2_print != NULL);
set_func d2_set = (set_func) dlsym(handle2, "dynamic_lib_2_set_global");
assert(d2_set != NULL);
enum GlobalType type;
printf("**************************************************\n");
printf("** FUNCTION GLOBAL\n");
printf("**************************************************\n");
type = kFunctionGlobal;
(d1_print)(type);
(d2_print)(type);
printf("** SET D1 TO 111 - THEN PRINT FROM D2\n");
d1_set(type, 111);
d1_print(type);
d2_print(type);
printf("** SET D2 TO 222 - THEN PRINT FROM D1\n");
d2_set(type, 222);
d2_print(type);
d1_print(type);
printf("**************************************************\n");
printf("** CLASS GLOBAL\n");
printf("**************************************************\n");
type = kClassGlobal;
(d1_print)(type);
(d2_print)(type);
printf("** SET D1 TO 111 - THEN PRINT FROM D2\n");
d1_set(type, 111);
d1_print(type);
d2_print(type);
printf("** SET D2 TO 222 - THEN PRINT FROM D1\n");
d2_set(type, 222);
d2_print(type);
d1_print(type);
printf("**************************************************\n");
printf("** FILE GLOBAL\n");
printf("**************************************************\n");
type = kFileGlobal;
(d1_print)(type);
(d2_print)(type);
printf("** SET D1 TO 111 - THEN PRINT FROM D2\n");
d1_set(type, 111);
d1_print(type);
d2_print(type);
printf("** SET D2 TO 222 - THEN PRINT FROM D1\n");
d2_set(type, 222);
d2_print(type);
d1_print(type);
return 0;
}