【问题标题】:Hiding symbols of the derived class in shared library在共享库中隐藏派生类的符号
【发布时间】:2021-11-04 08:54:34
【问题描述】:

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

但是,让我们从头开始。

假设:

  • GCC 工具链用于 Linux 操作系统
  • 库和客户端应用程序使用相同的工具链构建
  • 该库将用于使用相同工具链构建的 Linux 发行版
  • -fvisibility=hidden-fvisibility-inlines-hidden 编译选项用于构建库

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

案例一

客户端代码:

#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)是否可以只为基类和工厂函数导出符号,其中工厂返回隐藏符号的派生类的实例?

【问题讨论】:

  • 关于案例 4,我认为这是可行的,因为析构函数是使用虚拟方法表间接调用的。链接器不需要在链接时解析实际功能;这是在运行时完成的。

标签: c++ linker shared-libraries linker-flags


【解决方案1】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2012-11-27
    • 2013-06-03
    • 2012-03-27
    • 2014-12-07
    • 2019-09-26
    • 2010-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多