【问题标题】:Is there any preference linker gives to static symbols or dynamic symbols?链接器是否对静态符号或动态符号有任何偏好?
【发布时间】:2018-12-07 03:34:25
【问题描述】:

我有两个头文件和两个 cpp 文件:

//f1.h
int f1();

//f1.cpp
include "f1.h"
int f1() {return 1;}

//f2.h
int f2();

//f2.cpp
#include "f2.h"
#include "f1.h"
int f2() {return f1() + 1;}

//main.cpp
#include "f2.h"
int main() {return f2();}

首先,我从f1f2 编译一个共享对象,然后根据该共享对象从main.cpp 创建一个二进制文件:

g++ -c -fPIC -shared f1.cpp f2.cpp
g++ -shared -fPIC -o libf.so f2.o f1.o
g++ -o dynamic main.cpp libf.so

现在我对f1.cpp 进行一些更改(比如f1 现在返回2):

//f1.cpp#
include "f1.h"
int f1() {return 2;}

并按如下方式编译二进制文件:

g++ -o semistatic main.cpp f1.cpp libf.so

问题是“半静态”二进制文件是使用来自libff1() 定义(其中f1 返回1)还是使用静态链接符号(其中f1 返回@987654338 @)?这是否在不同系统中有所不同?我可以依靠这在单个系统中保持一致吗?

【问题讨论】:

标签: c++ dynamic static linker shared-libraries


【解决方案1】:

正如已经指出的那样,您违反了单一定义规则。这不是世界末日,但在这种情况下,C++ 标准无法保证会发生什么,并且行为取决于链接器和加载器的实现细节。

工具链和操作系统完全不同,因此上述内容甚至无法在 Windows 上链接。但是,如果您使用通常的链接器/加载器对谈论 Linux,那么行为将是使用更改后的版本 - 这将适用于每个 Linux 安装。

这就是链接器/加载器在 Linux 上的工作方式(这种行为被广泛用于例如LD_PRELOAD-trick):

  • *.so 中的符号很弱,因此如果链接器在其他地方找到另一个定义(在您的情况下是 f1.o 的更新版本),则只会忽略来自 *.so 的定义。
  • 在运行时,如果符号已经绑定,加载程序会忽略来自共享对象的定义,即另一个定义是已知的。在您的情况下,符号 f1 (好的,由于名称修改,它将具有不同的名称,但为了简单起见,我们忽略它)已经绑定到主程序中的定义,因此将在*.so 中调用f1 时使用。

但是,这种做事方式非常脆弱,一些细微的变化可能会导致不同的结果。

A:将可见性更改为隐藏。

建议隐藏不属于公共接口的符号,即

__attribute__ ((visibility ("hidden")))
int f1() {return 1;}

在这种情况下,使用的不是覆盖版本而是旧版本。不同之处在于,当链接器看到正在使用的隐藏符号时,它不再将其委托给加载器来解析符号的地址,而是直接使用手头的地址。稍后,我们无法更改调用的定义。

B:使f1 成为内联函数。

这会导致非常有趣的事情,因为在某些部分共享对象会使用旧版本,而在某些部分会使用新版本。

-fPIC 防止内联未标记为inline 的函数,因此上述仅适用于显式标记为内联的函数。


简而言之:这个技巧可以在 Linux 上使用。然而,在更大的项目中,您不希望有额外的复杂性并尝试坚持更可持续和简单的单一定义规则框架。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-01
    • 2014-01-03
    • 1970-01-01
    相关资源
    最近更新 更多