我有两个标头和两个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
)?跨系统是否不同?我可以依靠它在单个系统中保持一致吗?
答案 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
的函数的内联,因此以上内容仅适用于显式标记为内联的函数。