是否有任何首选项链接器给静态符号或动态符号?

sen*_*enx 7 c++ linker static dynamic shared-libraries

我有两个标题和两个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();}
Run Code Online (Sandbox Code Playgroud)

首先,我从f1和编译共享对象,并根据该共享对象f2创建二进制文件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
Run Code Online (Sandbox Code Playgroud)

现在我介绍一些更改f1.cpp(比如f1现在返回2):

//f1.cpp#
include "f1.h"
int f1() {return 2;}
Run Code Online (Sandbox Code Playgroud)

并编译二进制文件如下:

g++ -o semistatic main.cpp f1.cpp libf.so
Run Code Online (Sandbox Code Playgroud)

问题是'半静态'二进制是否会使用f1()from libf(在哪些f1返回中1)的定义,或者它将使用静态链接的符号(f1返回的符号2)?这是不同的系统,我可以依靠这在一个系统内保持一致吗?

ead*_*ead 3

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

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

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

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

然而,这种做事方式非常脆弱,一些微小的改变可能会导致不同的结果。

A:将可见性改为隐藏。

建议隐藏不属于公共界面的符号,即

__attribute__ ((visibility ("hidden")))
int f1() {return 1;}
Run Code Online (Sandbox Code Playgroud)

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

B:制作f1是一个内联函数。

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

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


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