C++描绘 Linux 动态库的最佳实践

当概念全局变量和函数是,如果我们以 static
关键字修饰他们,就光能够以和一个文件内援他们;如果我们不利用
static 关键字,就好以另外文件被引用他们。

然而,当落实动态库时,问题就是转换得有些复杂。

动态库的接口函数可以被动态库内的其他文件引用,也可以给外动态库引用。而动态库的中函数只能为与一个动态库内的别文件引用,不能够叫另外动态库引用。

对此“如何被函数可以被动态库内的外文件引用,而无克于另外动态库引用”的要求,static
关键字是无力回天的。

这时候,我们不怕需改符号的可见性(visibility)

符号

于 ELF 文件来说,程序中冒出的有着变量和函数都是符号(symbol)

变量所当的内存单元以及函数的函数体被称符号的定义(definition)

当我们采取 static 关键字修饰变量或者函数时,我们是当改符号的
binding(绑定关系)。在 C 语言中,我们普通号称作用域。

符号的 Binding

记一共来三种植 binding,分别是:

binding 含义
LOCAL 本地符号,只能在文件内被引用
GLOBAL 强全局符号,可以被其他文件引用,而且只能在一个文件中被定义
WEAK 弱全局符号,可以被其他文件引用,但是可以在多个文件中被定义

Local Symbol

使用 static 关键字修饰的全局变量和函数是 local symbol。

即好像标志只能当与一个文本中受引述,而不能够被其他文件引用。多只文本可以定义同名的
local 符号,但是这些标记不见面相影响。

一个动态库中的 local symbol 和外一个动态库的同名 local symbol
之间无会见相互影响。

Global Symbol

不使用 static 关键字修饰的全局变量和函数是 global symbol 。

及时看似标志能于其它文件被让引用,也堪外动态库引用。也就是说,这样的标记在全路过程空间内发生唯一的概念。

每当链接时,如果多个文件中定义了重名的 global 符号,就会见吸引链接错误。

每当动态加载时,如果多单动态库定义了重名的 global
符号,那么就是偏偏会保留其中的一个定义。这便代表,在顾与一个动态库内定义之
global 符号时,有或访问到之是其它动态库中之概念。

当 ELF 文件层面,在动态库中走访 global symbol 都得依赖 PLT 和
GOT,而休可知一直看,因此进度吗比看 local symbol 慢。

Weak 符号

当 C 和 C++ 程序中,有以下办法可以定义 weak symbol:

  1. 使用 __attribute__((weak)) 修饰的全局变量和函数是 weak symbol;
  2. C++ 库中的 operator newoperator delete 是 weak symbol;
    3.要定义了外联函数,但是该内联函数生成了一个独立的函数体,那么该符号为
    weak symbol;
  3. 在 C++ 中,在近似定义里一直定义之积极分子函数都从带 inline
    效果,因此为是 weak symbol;
  4. 函数模版实例化后的代码是 weak symbol。

Weak symbol
可以当差不多个公文中受定义,但是链接时只有出一个定义会叫封存。保留的平整是:

  1. 只要起差不多个同名的 weak symbol,那么符号长度最丰富之相会于保留。
    1. 于变量,就是大大小小最酷之概念会被保留。
    2. 对函数,就是函数体最丰富的定义会让保留。
  2. 假使有多单同名的 weak symbol 和一个 global symbol,那么深 global
    symbol 的定义会给保留。

为此,如果用户定义了 operator new
函数,那么链接器就会以用户定义的贯彻,而非是标准库中之落实。

符号的 Visiblity

为缓解全局符号可能于动态库之间彼此干扰的题目,ELF
引入了号的可见性(visibility)。

当链接成动态库或者可执行文件时,链接器根据符号的 visibility 修改它的
binding。

Visibility 一共发 7 种,但是常用的只有 default 和 hidden
两栽。它们的修饰符分别是:

  • __attribute__((visibility ("default")))
  • __attribute__((visibility ("hidden")))

默认的 visibility 是 default,但是可以在编译时传出命令执行参数
-fvisibility=hidden 将默认 visibility 设置为 hidden。

Default Visibility

当链接时,符号的 binding 保持无转移。

Visibility 为 default 的 global
符号可能为另外动态库的同名符号覆盖,导致在运转时看的凡其他动态库中之概念,而休该动态库内的定义。

常见,需要导出的标志的 visibility 为 default。

Hidden Visibility

顿时好像标志在链接成动态库或者可执行文件后,binding 会从 global 变成
local,同时 visibility 变成 default。

从而,这类标志只能于动态库内部被看,而未可知叫外动态库访问。

对于动态库或者可执行程序来说,所有未需导出的符的 visibility
都应该是 hidden。

至上实践

每当实现 C 和 C++ 的动态库时,使用 -fvisibility=hidden
来编译动态库

于概念 API 时,建议采用 DLL_PUBLICDLL_LOCAL
宏来支配符号的可见性,它于 Windows、Cygwin、Linux 和 macOS
上且足以正常干活:

#if defined _WIN32 || defined __CYGWIN__
  #ifdef BUILDING_DLL
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllexport))
    #else
      // Note: actually gcc seems to also supports this syntax.
      #define DLL_PUBLIC __declspec(dllexport)
    #endif
  #else
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllimport))
    #else
      // Note: actually gcc seems to also supports this syntax.
      #define DLL_PUBLIC __declspec(dllimport)
    #endif
    #define DLL_LOCAL
  #endif
#else
  #if __GNUC__ >= 4
    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define DLL_PUBLIC
    #define DLL_LOCAL
  #endif
#endif

于 C 中,可以运用这个宏导出函数和变量:

// 使用 DLL_PUBLIC 修饰需要导出的符号
DLL_PUBLIC int my_exported_api_func();
DLL_PUBLIC int my_exported_api_val;

// 不使用 DLL_PUBLIC 修饰动态库内部的符号,
// 因为默认可见性被修改为 hidden
int my_internal_global_func();

以 C++ 中,可以动用此宏来导出一个类似:

// 使用 DLL_PUBLIC 修饰需要导出的类
class DLL_PUBLIC MyExportedClass {
 public:
  // 类里面的所有方法默认都是 DLL_PUBLIC 的
  MyExportedClass();
  ~MyExportedClass();
  int my_exported_method();

 private:
  int c;

  // 使用 DLL_LOCAL 修饰动态库的内部符号
  DLL_LOCAL int my_internal_method();
};

参考阅读

Symbol Table
Section
ELF 文件被符号表的概念,详细描述了 binding 与 visibility。

Visibility
GCC wiki 中关于 visibility 的顶尖实践。