在共享库中隐藏派生 class 的符号

Hiding symbols of the derived class in shared library

我将编写一个共享库,并且我在 Internet 上找到了这个 note 关于设置符号可见性的内容。一般指导是隐藏库客户端不需要的所有内容,从而减少库的大小和加载时间。除非使用 class 层次结构,否则对我来说很清楚。

不过,还是从头说起吧。

假设:

我准备了案例来检查编译器切换到库的影响。

案例一

客户代码:

#include "Foo.hpp"

int main()
{
  auto s = make();
  delete s;
}

图书馆header:

struct Foo{};

Foo* make() __attribute__((visibility("default")));

图书馆来源:

#include "Foo.hpp"

Foo *make() { return new Foo; }

在这种情况下,所有内容都可以正确编译和链接,甚至只有 make 函数被导出。我很清楚。

案例二

我添加了 Foo 析构函数定义。

客户端代码同案例2

图书馆header:

struct Foo {
  ~Foo();
};

Foo* make() __attribute__((visibility("default")));

图书馆来源:

#include "Foo.hpp"

Foo::~Foo() = default;

Foo *make() { return new Foo; }

在这种情况下,在将客户端应用程序与库链接期间,链接器抱怨未定义对 Foo::~Foo() 的引用,这对我来说也很清楚。未导出析构函数符号,客户端应用程序需要它。

案例三

客户端应用程序和库源与案例 2 相同。但是,在库中 header 我导出 Foo class:

struct __attribute__((visibility("default"))) Foo {
  ~Foo();
};

Foo* make() __attribute__((visibility("default")));

这里没有惊喜。代码编译、链接和运行没有错误,因为库导出了客户端应用程序所需的所有符号。

但是(案例 4)

当我第一次尝试编写这个库时,我没有导出 class,而是将析构函数设为虚拟的:

struct Foo {
  virtual ~Foo();
};

Foo* make() __attribute__((visibility("default")));

令人惊讶的是……代码编译、链接和运行没有任何错误。

更进一步(案例5)

最初我的库定义了一个 class 层次结构,其中 Foo 是其余部分的基础 class 并定义了纯虚拟接口:

struct __attribute__((visibility("default"))) Foo {
  virtual ~Foo();
  virtual void foo() = 0;
};

Foo* make() __attribute__((visibility("default")));

make 函数生成派生的 classes 的实例,返回指向基 class:

的指针
#include "Foo.hpp"

#include <cstdio>

Foo::~Foo() = default;

struct Bar: public Foo
{
  void foo() override { puts("Bar::foo"); }
};

Foo* make() { return new Bar; }

并且应用程序使用接口:

#include "Foo.hpp"

int main()
{
  auto s = make();
  s->foo();
  delete s;
}

一切都编译、链接、运行没有错误。应用程序打印 Bar::foo,这表明调用了 Bar::foo 覆盖,甚至根本没有导出 Bar class。

问题

  1. (案例4)为什么链接器没有向析构函数抱怨缺少符号?这是一种未定义的行为吗?我不打算那样做,只是想了解一下。
  2. (案例 5)是否可以只为基础 class 和工厂函数导出符号,其中派生的 classes 的工厂 returns 实例符号被隐藏了?

Why did the linker not complain about the missing symbol to the destructor?

这是因为客户端代码从存储在 make 函数中创建的对象中的 vtable 中加载析构函数的地址。链接器在链接客户端代码时不需要知道 ~Foo 的显式地址。

Is it ok to export symbols only for the base class and the factory function, where the factory returns instances of the derived classes for which the symbols are hidden?

只要您的客户端代码只调用这些派生 类 中的重载虚方法就可以了。原因与虚拟 ~Foo 相同 - 地址将从对象的 vtable 中获取。