参考动态链接 - 知乎
加上我自己的理解,比较好懂,但可能在细节方面有偏差,但总体是一致的
静态链接的背景
静态链接使得不同的程序开发者和部门能够相对独立的开发和测试自己的程序模块,从某种意义上来讲大大促进了程序开发的效率,原先现在程序规模也随之扩大。
但静态链接的缺点也暴露出来:浪费内存、磁盘空间、模块更新困难(耦合性太强)。
内存与磁盘空间
静态链接在计算机早期还是比较流行的,但是到了后面,其缺点也非常明显。比如浪费内存和磁盘空间,更新模块困难等。
一个文件的转换进程
hello.c(源程序[文本])->预处理器(cpp)->hello.i(修改了的源程序[文本])->编译器(ccl)->hello.s(汇编程序[文本])->汇编器(as)->hello.o(可重定位目标程序[二进制])->链接器(ld)->hello(可执行目标程序[二进制])
只需要注意:
hello.c:源文件
hello.o:由hello.c编译而来
hello(可执行目标程序[二进制]),最终执行文件。
比如我有a.cpp ,a.h,b.cpp c.cpp四个文件
a.cpp 定义了一个函数sayHello
#include <iostream>
void sayHello(){
std::cout<<"hello world!";
}
a.h
#ifndef UNTITLED1_A_H
#define UNTITLED1_A_H
void sayHello();
#endif //UNTITLED1_A_H
b.cpp 调用了a的函数sayHello
#include "a.h"
int main(){
sayHello();
}
c.cpp 调用了a的函数sayHello
#include "a.h"
int main(){
sayHello();
}
静态链接
如果运行b那么就会结合b.o,以及其调用的a.o生成可执行文件。c运行会另外调用 c.o,a.o生成可执行文件,此时可执行文件可以认为长这样
#include <iostream>
void sayHello(){
std::cout<<"hello world!";
}
int main(){
sayHello();
}
此时a.o重复了两次,但这两个a.o保存了相同的内容,浪费内存和磁盘。
程序开发与发布
静态链接另一个问题是对程序的更新,部署和发布也会很麻烦,我如果在a中更新了sayHello,那么生成的b,c可执行文件里的sayHello还是老的,需要重新连接,很麻烦耦合度很高。
程序有任何模块更新,整个程序就要重新链接,发布给用户,
动态链接
dll文件和so文件
dll文件和so文件都是动态链接库,也叫共享对象文件
动态链接原理
动态链接库就是将程序模块相互独立的分隔开来,形成独立的文件,不再将它们静态地链接到一起。简单而言就是对那些组成程序目标文件的链接,等到程序运行时才进行链接,也就是把链接的过程推迟到运行时才进行,这就是动态链接的基本思想。
动态链接没有提前链接的过程,但一般文件都是动态和静态链接结合使用,也有生成可执行文件这一步。
比如我编译a.cpp为共享对象文件a.so,还有a.h
b.cpp为c.o,此时我运行生成可执行文件,生成时侦测到sayHello为动态链接的实现,所以就不执行链接(如果侦测到为静态文件,那么就还是老样子进行链接)
b可执行文件执行时,遇到sayHello,就去动态库so中找这个函数的实现位置,这个找的位置有几类,我们经常用的是一个环境变量,叫LD_LABRARY_PATH,假设这里找到了,找到之后就将a.so加载到内存,链接,进行执行。
c执行时再用到a.so,此时a.so以及被加载到了内存中,所以不用再重新加载了,直接链接就能使用了。
那也就说明了动态链接的其他特点:
动态运行b.cpp之前必须要有a.so和a.h才能运行,所需的动态库本地必须齐全。
动态链接库的存储位置以及名称
lib文件的名称,要和头文件相呼应,但有些库文件并不总是和头文件相呼应,连接器也不是通过头文件名来搜寻库文件的,而是根据将所有要搜寻路径库文件按优先级列出来,然后一个一个查找。
比如
cudnn.h对应libcudnn.so
cudnn_adv_infer.h对应libcudnn_adv_infer.so等等
一般so文件会存在lib下,头文件存在include下
如cuda11.6
可以看到
再比如我们c++原生的一些实现
头文件也是在include下,可以看到我们的老熟人头文件iostream等等
模块
静态链接中,整个程序最终只有一个可执行文件,它是一个不可以分割的整体,但是在动态链接下,一个程序被分成了若干个文件,有程序的主要部分,即可执行文件(Program1)和
程序所依赖的共享对象(Lib.so),很多时候把这些部分叫做模块,即动态链接下的可执行文件和共享对象都可以看做是程序的一个模块
当程序模块Program1.c被编译成Program1.o时,编译器还不不知道foobar函数的地址,当链接器将Program1.o链接成可执行文件时,这时候链接器必须确定Program1.o中所引用的foobar函数的性质。
- 如果foobar是一个定义与其它静态目标模块中函数,那么链接器将会按照静态链接的规则,将Program1.o中的foobar地址引用重定位
- 如果foobar是一个定义在某个动态共享对象中的函数,那么链接器就会将这个符号的引用标记为一个动态链接的符号,不对它进行地址重定位,把这个过程留到装载时再进行
程序如何找到用到的动态库以及查看用到的动态库
动态库的搜索顺序如下:
- LD_PRELOAD环境变量指定库路径
- -rpath链接时指定路径
- LD_LIBRARY_PATH环境变量设置路径
- /etc/ld.so.conf配置文件指定路径
- 默认共享库路径,/usr/lib,lib
应该就是按顺序一个一个寻找,直到找到满足要求的实现的动态库为止。