什么是符号隐藏
在同一个文件中,如果有一些函数我们并不想要让外部访问,我们通常会添加 static 修饰符,把它设置为内部链接属性。
1 | static void foo(); |
但是通常库不太可能是单文件组成,这些文件中有些是做接口给外部使用,有些则单纯的只是库的内部实现。对于外部使用者来说,内部实现的这些符号没有实际的作用,理论上我们完全可以像对待文件内部符号一样把它们统统隐藏掉。但是在语言层面我们并没有相关的语法用于表达这个概念(Java中的包访问权限和C#中的internal类似这个概念)。不同的编译器提供了不同的方式来完成这件事情,这篇文章总结了一种跨平台的处理方式。
符号隐藏的作用
一般来说做符号隐藏有以下三个作用:
- 安全,去掉不必要的符号,可以增加逆向破解的难度。
- 压缩空间,符号实际上是放在 dll 中的,去掉这些符号可以缩减 dll 的大小
- 性能,符号隐藏掉意味着它不会参与到动态链接过程,编译器可以有更大的优化空间,可能会产生更好的性能。
如何做符号隐藏
符号隐藏可以采用下面几个步骤(文中假定你使用MSVC或者4.0以上版本的GCC,低版本GCC不支持符号隐藏):
1. 动态库
符号能否隐藏在于它在动态链接的过程中是否需要用到。静态库实际上是目标文件的集合,它并没有完成链接过程。所以符号隐藏通常都是基于动态库的,静态库的符号隐藏没有很好的跨平台方式,如果想要尝试,可以参考下面这些链接。
2. 默认隐藏所有的符号
MSVC和GCC在动态库符号的默认属性上面有较大的差别,MSVC默认所有的符号都是隐藏的,而GCC默认所有的符号都是可见的。虽然我不太喜欢臃肿的MSVC,但是我不得不承认在这一点上,我更倾向于MSVC的选择。
如果你使用MSVC
编译器,这个步骤你可以什么都不做,如果你使用GCC,你需要给你的编译器加上-fvisibility=hidden
选项,你也可以加上-fvisibility-inlines-hidden
把内联函数隐藏掉。如果你使用Autotool
,你可以通过设置LD_CXXFLAG
来控制默认隐藏,如果你使用CMake
,可以通过set(CMAKE_CXX_VISIBILITY_PRESET hidden)
来完成这一点。
3. 把你想要公开的接口的属性设置为外部可见
在MSVC中,我们通过在编译的时候设置__declspec(dllimport)
和使用的时候设置__declspec(dllexport)
来完成这一点,在GCC中则简单一些统一设置成__attribute__ ((visibility ("default")))
即可。
辅助宏
上面这些步骤比较繁琐,通常会定义宏来协助处理这一部分内容,下面是来自GCC WIKI
的一个模板
1 | // Generic helper definitions for shared library support |
我们想要导出一个符号的时候使用FOX_API
:
1 | class FOX_API Fox {}; |
在编译动态库的时候,设置FOX_DLL
和FOX_DLL_EXPORTS
这两个宏。在使用动态库的是,定义FOX_DLL
这个宏。