Clang动态库默认导出所有非static全局符号,必须启用-fvisibility=hidden并配合__attribute__((visibility("default")))显式导出接口,否则将引发符号膨胀与跨库重名冲突。

Clang动态库默认导出所有非static全局符号
Clang(和GCC)在未加干预时,会把每个翻译单元里所有非 static 的全局函数、变量都写进动态库的动态符号表(DSO symbol table),哪怕它们只是内部辅助函数。这直接导致两个后果:一是符号膨胀,二是跨库重名冲突——比如两个库都定义了 log_message(),加载时就可能被后加载的覆盖或报 undefined symbol 错误。
-fvisibility=hidden 是必须打开的编译开关
不加这个选项,__attribute__((visibility("default"))) 就是摆设。它只在 -fvisibility=hidden 生效的前提下才有意义。
- 在 CMake 中添加:
set(CMAKE_CXX_VISIBILITY_PRESET hidden)或手动给target_compile_options(mylib PRIVATE -fvisibility=hidden) - 命令行编译时加:
clang++ -fPIC -fvisibility=hidden -shared -o libmy.so src.cpp - 注意:该选项不影响
static函数/变量(它们本就不导出),也不影响extern "C"块内未显式标记的符号——它们仍按默认 visibility 处理
用 __attribute__((visibility("default"))) 显式标记导出接口
只对真正需要被外部调用的函数或类加这个属性;其余一律不标,靠 -fvisibility=hidden 自动屏蔽。
- 导出单个函数:
extern "C" __attribute__((visibility("default"))) int mylib_init(); - 导出整个类(慎用):
class __attribute__((visibility("default"))) MyLibAPI { ... };——但要注意成员函数仍需单独标记,或改用 PIMPL 隐藏实现 - 宏封装更安全:
#define MYLIB_API __attribute__((visibility("default"))),然后写MYLIB_API void process(); - 别忘了头文件里也得有同样声明,否则调用方看不到符号
检查导出符号是否符合预期
编译完别急着集成,先用工具确认哪些符号真进了动态库:
- 看动态符号表:
nm -D libmy.so | c++filt | grep 'T '(T表示全局文本段函数) - 更精确地过滤导出函数:
readelf -Ws libmy.so | awk '$4 == "GLOBAL" && $7 == "FUNC" {print $8}' - 如果看到不该出现的函数名(比如
internal_helper),说明它没被隐藏——检查是否漏了-fvisibility=hidden,或该函数被extern "C"包裹却没加visibility("default")
最容易忽略的是:C++ 类成员函数即使在头文件里定义为 inline,只要没显式加 visibility 属性且没被 -fvisibility=hidden 覆盖,Clang 仍可能导出其修饰名——尤其当它被 ODR-used 且定义在头中时。


















