根本原因是未启用-fvisibility=hidden编译选项或属性标注位置错误:必须全局开启该选项,且__attribute__((visibility("hidden")))需紧贴定义或声明,覆盖符号全生命周期。

为什么加了 visibility(hidden) 还是导出了符号?
根本原因通常是编译器没真正启用 visibility 控制,或者你标记的位置不对。GCC/Clang 默认不开启 -fvisibility=hidden,光在代码里写 __attribute__((visibility("hidden"))) 没用——它只对「已启用 visibility 的编译单元」生效。
- 必须加编译选项:
-fvisibility=hidden(全局默认隐藏),再用default显式标出要导出的符号 -
visibility(hidden)是函数/类/变量的属性,不是链接器开关,不能后加到 .so 上 - 头文件里声明的 inline 函数、模板实例化、内联友元函数,即使加了 hidden 属性,也可能因 ODR 或隐式实例化被导出
- C++ 类的虚表(vtable)、typeinfo、RTTI 信息默认仍会导出,除非额外加
-fvisibility-inlines-hidden
__attribute__((visibility("hidden"))) 放哪儿才生效?
它必须紧贴「定义」或「声明」,且作用域要覆盖整个符号生命周期。常见误放位置:头文件中只声明不定义、放在 class 定义外但没加到每个成员上、放在模板声明里却忘了显式实例化点。
- 对普通函数:放在函数定义前,如
__attribute__((visibility("hidden"))) void helper(); - 对类:推荐在类定义前加,这样所有成员(含生成的 ctor/dtor/vtable)都受控:
class __attribute__((visibility("hidden"))) Foo { ... }; - 对模板:属性只能加在显式实例化声明上,比如
template __attribute__((visibility("hidden"))) class std::vector<int>;</int> - 避免在头文件中对非 inline 符号加 hidden 属性——如果多个 TU 包含该头,可能引发 ODR 冲突
如何验证符号是否真被隐藏了?
别信编译日志,直接查生成的 .so。nm -D 看动态符号表,objdump -T 更准;注意区分 T(代码)、D(数据)、U(未定义)和 W(weak)。
- 导出符号应出现在
nm -D libxxx.so输出里;hidden 符号通常完全不出现,或仅以小写t/d形式存在于静态符号表(nm -C libxxx.so) - 用
readelf -d libxxx.so | grep NEEDED确认没意外依赖其他未隐藏的符号 - 如果某个函数仍出现在
nm -D中,检查它是否被标记为extern "C"(C linkage 会绕过 C++ visibility 规则) - 调试时可临时加
-frecord-gcc-switches,确认编译命令确实带了-fvisibility=hidden
和 visibility(default) 混用时最容易漏什么?
显式标 default 是为了“白名单”导出,但漏标一个关键符号,调用方就直接 undefined symbol。这不是编译错误,而是运行时报错,非常难定位。
- 所有需要被外部 .so 或主程序调用的函数、类、全局变量,都必须显式加
__attribute__((visibility("default"))) - 类的 public 成员函数不自动导出,哪怕类本身是 default——必须每个函数单独标,或用宏封装
- 如果用了宏包装(如
#define API_PUBLIC __attribute__((visibility("default")))),确保宏在定义时展开,而不是仅在声明时存在 - 注意构造函数和析构函数:若类是 default,但 ctor/dtor 是 inline 且没显式标 default,可能因编译器优化被隐藏(尤其在 -O2 下)
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











