Explicit-Template-Instantiation

模板显式实例化

背景

目前 Topling SidePlugin 直接编译到 librocksdb.so 中,之所以这么做,是因为对于静态库,链接器会自动删除它认为无用的符号,如果链接器一个编译单元内的所有符号都没有被使用,整个编译单元的代码都会被删除。要解决这个问题,可以使用 --whole-archive,但这样又会把大量真正的无用代码也链接进去。

目前我们尚未找到可以完美解决这个问题的办法,所以我们使用动态库 librocksdb.so

然后在使用动态库时又遇到一个问题:对于头文件中模板类的函数内的静态成员,在多个 so 的时候,会出问题,在我们的场景中,除了 librocksdb.so,还有一个用户插件库 libuserplugin.so,在这两个 so 中,都会(通过全局对象的构造/析构)自动注册 plugin,这些 plugin 在析构时会出现 double free 问题。

分析

经过诊断分析,基本确认是编译器/链接器的不完善导致的,因为根据推测:

  1. 多个编译单元会有相同的 template 实例化的代码(表现为相同的函数标识/符号)
  2. 链接时,对于重复的符号,只保留一份
  3. 链接时,如果输入的 .o 文件和 .so 文件中有相同的符号,链接器会采用 .so 中的符号,丢弃 .o 中的符号(及对应的代码、对象)
  4. 根据依赖关系,先构造的后析构,依赖关系末端 .so 中的对象会最后析构

如果以上 4 点全部成立,是不会有问题的。然而程序的表现是,构造(注册插件)时并未出问题,但在析构时出现 double free 错误,所以实际情况至少与以上 3、4 条中的一条不符。

解决

显然我们不可能改编译器,我们也不大可能给编译器提交 bug 然后坐等 bug 修复。

我们采用的方法是:显示实例化,头文件 只包含模板声明,在一个单独的 side_plugin_tpl_inst.cc 文件中包含模板的实现以及显式实例化。

显式实例化不光解决了 double free 这个问题,还附带产生另外的收益:

  1. 发现并修复其它 bug:隐式实例化了错误的类型
  2. 减小了目标代码的尺寸
  3. 加快了编译速度