链接器对静态符号或动态符号有什么偏好?

时间:2018-06-28 06:45:50

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

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

> set.seed(7)
> unique(sample(rep(1:10), 25, replace = TRUE))
[1] 10  4  2  1  3  8  5  6  7

首先,我从//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();} 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 进行一些更改(例如f1.cpp现在返回f1):

2

并按如下所示编译二进制文件:

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

问题是“半静态”二进制文件将使用g++ -o semistatic main.cpp f1.cpp libf.so f1()的定义(其中libf返回f1)还是使用静态链接符号(一个其中1返回f1)?跨系统是否不同?我可以依靠它在单个系统中保持一致吗?

1 个答案:

答案 0 :(得分:1)

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

工具链和操作系统完全不同,因此以上内容甚至在Windows上也不会链接。但是,如果您使用的是通常的链接器/加载器对,那么,这将是使用更改后的版本-每次安装Linux时都会使用。

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

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

但是,这种处理方式非常脆弱,有些微小的变化可能会导致不同的结果。

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

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

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

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

B:使f1是内联函数。

这将导致非常有趣的事情,因为在某些情况下将使用旧版本的共享库,而在某些情况下将使用新版本。

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


简而言之:此技巧可在Linux上使用。但是,在较大的项目中,您不想再增加复杂性,而是尝试坚持更具可持续性和简单性的单定义规则框架。