__attribute__((visibility("hidden")))
是GCC拓展功能。作用简单来说就是隐藏符号导出,使符号对外部不可见。但是static同时存在这一作用,所以接下来,我会记录尝试区分static和这一拓展功能的相同作用和部分区别的过程。
开始
根据c - What is the practical difference between a static function and a function with the “hidden” visibility attribute? - Stack Overflow回答,__attribute__((visibility("hidden")))
对共享库外不可见,如果构成一个共享库的源代码文件为多个,且一个源代码文件中具有此指令修饰的函数,那么另外的构成该共享库的文件可以在声明后调用该函数而不会找不到符号。
static关键字并不相同。static修饰的函数作用范围仅在这一源文件中,对源文件外不可见。但是构建共享库时并不会因为跨源文件调用static修饰的函数而报错,只有在共享库和别的目标文件链接成真正的可执行程序时才会报错。
接下来我会实践从ELF文件的符号解析上简单理解为什么会有这个结果。
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
生成目标文件
gcc -c attr_a.c
gcc -c static_a.c
gcc -c b.c
gcc -c c.c
gcc -c d.c
查看对应文件目标文件符号表
1 |
|
1 |
|
可以看到使用__attribute__((visibility("hidden")))
修饰的函数所在的源文件生成的目标文件的对应函数名的符号的Vis为HIDDEN
,实际上这一关键字的作用就是将修饰的符号(各种类型比如函数变量结构体等最终生成的用于标记的符号)的Vis字段修改为HIDDEN
。
生成共享库
执行gcc -shared -fPIC -o static_out.so static_a.o b.o
(虽然经过测试在我的x86-64环境中加不加-fPIC参数出来的动态库的SHA1值一模一样,但是看到的问答加上比较好)生成包含static修饰的函数的动态库。
执行gcc -shared -fPIC -o attr_out.so attr_a.o b.o
生成包含__attribute__((visibility("hidden")))
修饰的函数的动态库。
查看符号表
使用readelf -s
查看符号表,经过过滤后的输出为
1 |
|
1 |
|
可以看到使用__attribute__((visibility("hidden")))
的动态库中对应符号从目标文件中的Bind: GLOBAL Vis:HIDDEN
变为Bind:Local Vis:DEFAULT
。
而使用static关键字的动态库中包含了两个符号,可以看出来一个是b.c源文件中声明并使用的,它的Type为NOTYPE。而另一个是使用static修饰的函数,它的Bind为local。在链接时会报错未定义引用。
静态链接
gcc d.o attr_a.o -o attr.out
1 |
|
最终符号回归到和static相同的表现。
结论
使用__attribute__((visibility("hidden")))
指令,在生成目标文件时会将该指令修饰的符号的Vis
属性修改为hidden
,bind
为global
。在链接时该符号可以被正常识别和重定位,但在链接后生成的动态库或者可执行文件时该符号被修改为Bind:Local Vis:DEFAULT
。这一结果导致使用该指令修饰的源码的符号生成为动态库时始终为Bind:Local。与static修饰的符号的属性结果相同。导致无法在动态库外访问该符号。但是在链接时可以正常访问。
其他
c++ - What does -fPIC mean when building a shared library? - Stack Overflow