文章

C/C++在Linux符号冲突问题

C/C++在Linux符号冲突问题

该文主要介绍在 Linux 下使用 C/C++ 遇到符号(函数、变量)同名导致链接或运行时出错。

C/C++在Linux符号冲突问题

1. 问题场景

1.1. 两个动态库导出同名符号

程序同时链接 libA.solibB.so,两者都导出同名函数 bar。动态链接器默认使用先加载的符号。

1.2. 主程序与动态库符号冲突

主程序定义了一个函数 foo,动态库也导出了 foo,动态库调用时可能意外调用主程序的版本。

2. 解决方案

2.1. 两个动态库导出同名符号

动态加载时隔离(dlopen + RTLD_LOCAL

1
2
3
4
5
6
7
8
9
void* handleA = dlopen("./libA.so", RTLD_LAZY | RTLD_LOCAL);
void* handleB = dlopen("./libB.so", RTLD_LAZY | RTLD_LOCAL);
// RTLD_LOCAL 确保符号不污染全局命名空间
void (*funcA)() = (void(*)())dlsym(handleA, "common_name");
void (*funcB)() = (void(*)())dlsym(handleB, "common_name");
funcA();
funcB();
dlclose(handleA);
dlclose(handleB);

2.2. 主程序与动态库符号冲突

链接动态库时使用 -Bsymbolic,让库内部的符号引用优先绑定到库内部版本

1
2
3
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-Bsymbolic")
# 或者
g++ -shared -Wl,-Bsymbolic -o libmine.so file1.cpp file2.cpp

3. 总结

特性-Wl,-BsymbolicRTLD_LOCAL
作用时机链接时(构建库时)运行时(加载库时)
作用对象动态库内部动态库加载方式
修改位置库文件本身(写入 .so)调用 dlopen() 的代码
作用范围全局影响该库的所有使用仅影响当前 dlopen 加载的库
是否需要改代码不需要(编译选项)需要(显式调用 dlopen
典型用途库内部符号优先绑定自己隔离库的全局符号污染
标志作用默认值
RTLD_LAZY / RTLD_NOW符号解析时机(延迟/立即)无默认,必须二选一
RTLD_LOCAL / RTLD_GLOBAL符号可见性(隐藏/全局)RTLD_LOCAL

3.1. 使用场景

插件系统:所有插件使用统一的导出符号,主程序加载时需要独立加载使用 dlopen(path, RTLD_NOW | RTLD_LOCAL),这样就不会相互影响;

中间件系统:在插件系统基础上,中间件导出符号也和各个插件导出符号一致,需要使用 -Wl,-Bsymbolic ,底层插件优先使用内部函数,这样就不会导致调用异常;

参考

本文由作者按照 CC BY 4.0 进行授权