首页 > 解决方案 > 在共享库中隐藏派生类的符号

问题描述

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

但是,让我们从头开始。

假设:

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

情况1

客户端代码:

#include "Foo.hpp"

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

库头:

struct Foo{};

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

图书馆来源:

#include "Foo.hpp"

Foo *make() { return new Foo; }

在这种情况下,一切编译和链接都没有错误,甚至只make导出函数。这对我来说很清楚。

案例2

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

客户端代码与案例 2 相同。

库头:

struct Foo {
  ~Foo();
};

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

图书馆来源:

#include "Foo.hpp"

Foo::~Foo() = default;

Foo *make() { return new Foo; }

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

案例3

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

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

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

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

但是(案例 4)

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

struct Foo {
  virtual ~Foo();
};

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

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

走得更远(案例5)

最初我的库定义了一个类层次结构,Foo其余的基类在哪里,并定义了纯虚拟接口:

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

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

make函数产生派生类的实例,返回一个指向基类的指针:

#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根本没有导出类。

问题

  1. (案例 4)为什么链接器没有向析构函数抱怨缺少符号?这是一种未定义的行为吗?我不会那样做,只是想明白。
  2. (案例5)是否可以只为基类和工厂函数导出符号,工厂返回隐藏符号的派生类的实例?

标签: c++linkershared-librarieslinker-flags

解决方案


为什么链接器没有向析构函数抱怨缺少符号?

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

只为基类和工厂函数导出符号是否可以,工厂返回隐藏符号的派生类的实例?

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


推荐阅读