Linux下动态库全局变量必须用__attribute__((visibility("default")))显式导出,否则主程序无法访问;头文件需extern声明,源文件需EXPORT定义,编译时加-fvisibility=hidden;Windows则用__declspec(dllexport/dllimport)配合宏切换。

动态库里定义全局变量必须加 __attribute__((visibility("default")))
Linux 下 GCC 默认隐藏所有符号,动态库里的全局变量不显式导出,主程序 dlopen 或直接链接都看不到它。即使你写了 int g_counter = 0;,链接时也会报 undefined reference,运行时 dlsym 返回 NULL。
正确做法是显式标记可见性:
// shared.h
#ifdef __GNUC__
#define EXPORT __attribute__((visibility("default")))
#else
#define EXPORT
#endif
<p>extern EXPORT int g_config_flag;
extern EXPORT const char* g_version_str;
// shared.c #include "shared.h" EXPORT int g_config_flag = 1; EXPORT const char* g_version_str = "v2.1.0";
- 必须在头文件中用
extern EXPORT声明,否则主程序编译不过(缺少声明) - 必须在源文件中用
EXPORT定义,否则链接器看不到该符号 - 编译动态库时要加
-fvisibility=hidden(推荐),否则所有符号都暴露,易冲突
Windows 上要用 __declspec(dllexport) 和 __declspec(dllimport)
MSVC 不认 visibility,得靠 Windows 特有关键字。主程序和动态库共用同一头文件,但宏要根据编译上下文切换:
#ifdef _WIN32
#ifdef BUILDING_MYDLL
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
#else
#define DLL_API
#endif
<p>extern DLL_API int g_log_level;
-
BUILDING_MYDLL是你动态库项目里定义的宏,仅在编译 .dll 时启用 - 主程序不定义该宏,自动走
dllimport分支,链接器才知道去 .lib 里找符号 - 漏掉
dllimport可能导致访问变量时多一次间接跳转(性能微损),但功能仍正常;漏掉dllexport则彻底不可见
跨文件访问时,extern 声明不能少,且不能带初始化
主程序里写 int g_config_flag = 42; 是错的——这会定义一个新变量,覆盖动态库里的那份,造成两份内存、值不同步。正确写法只有声明:
// main.c
#include "shared.h" // 包含 extern 声明
int main() {
g_config_flag = 2; // 直接赋值,改的是动态库里的变量
return 0;
}
-
extern关键字必须出现,哪怕头文件里已有extern,也建议在 .c 文件顶部再写一遍,避免头文件被意外修改后失效 - 任何带
=的语句都是定义,不是声明;一旦在主程序里定义了同名变量,链接时可能静默成功(弱符号),但行为不可控 - 如果变量是
const,GCC 可能把它放进只读段,运行时改写会触发Segmentation fault
静态初始化顺序不可靠,别让全局变量互相依赖
动态库自身的多个全局变量之间,若存在跨编译单元依赖(比如 file1.c 里 int a = b + 1;,而 b 在 file2.c 定义),GCC 不保证初始化顺序。结果可能是 a 初始化为 1(b 还是 0)或随机值。
- 同一源文件内按书写顺序初始化,没问题
- 不同源文件之间顺序由链接器决定,实际取决于
.o文件传给ld的顺序,不可预测 - 真正安全的做法:把初始化逻辑收进函数,比如
init_config(),在main()开头或DLL_PROCESS_ATTACH里显式调用
最易被忽略的一点:动态库加载时机影响变量可见性。如果用 dlopen(RTLD_LAZY),符号直到首次调用才解析;而直接链接时,所有全局变量在 main() 执行前就已就位。这两者对初始化时机的假设完全不同。


















