__attribute__((visibility))用于控制elf/mach-o平台函数/变量的动态符号可见性:default可导出,hidden不进动态符号表,protected仅elf支持且防plt覆盖;配合-fvisibility=hidden全局设为隐藏并显式标记default接口最安全。

用 __attribute__((visibility)) 控制函数/变量的默认可见性
Clang(LLVM前端)支持 GNU 风格的 visibility 属性,这是最常用、最直接的方式。它作用于声明,而非定义,且只影响 ELF 平台(Linux/Android)和 Mach-O(macOS)的动态符号表生成。
常见写法:
-
__attribute__((visibility("default"))):显式导出,等同于不加修饰(但更明确) -
__attribute__((visibility("hidden"))):符号不进入动态符号表,其他模块无法dlsym或直接链接引用 -
__attribute__((visibility("protected"))):仅限 ELF;符号进动态符号表,但不可被外部覆盖(防止 PLT 覆盖)
注意:hidden 不影响静态链接——同一可执行文件内仍可正常调用,只是“对外隐身”。实际项目中常配合编译选项 -fvisibility=hidden 全局设为 hidden,再对需要导出的符号显式加 default,避免漏标。
-fvisibility= 编译选项的全局行为与陷阱
这个选项设置整个翻译单元(即每个 .cpp 文件)中未显式标注 visibility 属性的符号默认可见性。它不是链接期行为,而是在 Clang 生成 IR 时就固化到全局值的 visibility 字段中。
-
-fvisibility=default:恢复传统行为(所有符号默认可导出),但容易造成符号污染 -
-fvisibility=hidden:推荐用于库开发;但必须确保所有 ABI 接口函数都加了__attribute__((visibility("default"))),否则会静默“消失”
容易踩的坑:-fvisibility=hidden 对 static 函数/变量无影响(它们本就不生成全局符号);但它会影响 inline 函数——如果 inline 函数没加 default,其外联实例(out-of-line copy)也会被标记为 hidden,导致链接失败。
在 LLVM IR 中直接指定 visibility 属性
如果你手写或通过 API 构建 IR(比如用 llvm::IRBuilder),可在定义全局值时显式设置 visibility。IR 级别对应的是 GlobalValue::VisibilityTypes 枚举:
-
DefaultVisibility→ IR 中写作visibility = "default" -
HiddenVisibility→visibility = "hidden" -
ProtectedVisibility→visibility = "protected"
示例(IR 片段):
define hidden i32 @helper_func() { ... }
或
@global_var = hidden global i32 42
注意:IR 中的 hidden 会被后端(如 llc)忠实映射为目标文件的符号绑定(ELF 中 STB_LOCAL 或 STV_HIDDEN),但最终是否生效还取决于链接器是否保留该属性(例如 LLD 默认尊重,GNU ld 需配合 --hidden 等选项)。
链接时符号可见性可能被覆盖的几个关键点
即使源码和 IR 都设了 hidden,以下情况仍可能导致符号意外暴露:
- 使用
ld的--export-dynamic(或-E):强制把所有全局符号加入动态符号表 - 链接器脚本中用了
PROVIDE或EXTERN声明同名符号 - 多个目标文件中同名
weak符号 +default可见性,链接器选中的那个会暴露 - Clang 使用
-fPIC时,某些旧版工具链对hidden处理不一致(建议升级到 Clang 14+)
真正可靠的验证方式不是看源码或 IR,而是生成目标文件后用 readelf -sW foo.o | grep your_symbol 检查符号的 Bind(LOCAL / GLOBAL)和 Vis(DEFAULT / HIDDEN)列。











