写 Linux 动态库的超级实践

在概念全局变量和函数是,如若大家采取 static
关键字修饰他们,就只好够在同一个文书内引用他们;假若大家不应用
static 关键字,就能够在其他文件中引用他们。

C语言,不过,当落到实处动态库时,难点就变得多少复杂。

动态库的接口函数能够被动态库内的任何文件引用,也足以被其余动态库引用。而动态库的当中等高校函授数只能被同三个动态库内的其余文件引用,不可能被别的动态库引用。

对此“怎样让函数能够被动态库内的任何文件引用,而不可能被其余动态库引用”的急需,static
关键字是无力回天的。

那儿,大家就供给修改符号的可见性(visibility)

符号

对此 ELF 文件来说,程序中出现的全体变量和函数都是符号(symbol)

变量所在的内部存款和储蓄器单元和函数的函数体被称作符号的定义(definition)

当大家应用 static 关键字修饰变量大概函数时,我们是在修改符号的
binding(绑定关系)。在 C 语言中,大家常见称为效能域。

符号的 Binding

标记1共有三种 binding,分别是:

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

Local Symbol

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

那类符号只可以在同四个文书中被引用,而不能够被此外文件引用。几个公文能够定义同名的
local 符号,不过那些标记不会相互影响。

一个动态库中的 local symbol 和另二个动态库的同名 local symbol
之间不会相互影响。

Global Symbol

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

那类符号能在别的文件中被引用,也足以此外动态库引用。也正是说,那样的号子在全体过程空间内有唯1的定义。

在链接时,假若三个文本中定义了重名的 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 和2个 global symbol,那么那么些 global
    symbol 的概念会被封存。

由此,假设用户定义了 operator new
函数,那么链接器就会接纳用户定义的达成,而不是标准库中的完成。

符号的 Visiblity

为了化解全局符号或许在动态库之间相互苦恼的标题,ELF
引进了标记的可见性(visibility)。

在链接成动态库大概可执行文件时,链接器遵照符号的 visibility 修改它的
binding。

Visibility 一共有 七 种,但是常用的只有 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++ 中,能够动用这么些宏来导出2个类:

// 使用 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 的特等实践。