静态库与动态库

2021年6月4日 192点热度 1人点赞 0条评论

静态库与动态库

由文章C++编译过程可知,C++源文件编译的过程要经过以下四个步骤:
$$
预处理 \rightarrow 编译 \rightarrow 汇编 \rightarrow 链接
$$
其中在编译和链接的过程中,我们常常需要与静态库和动态库打交道。

在此之前,先了解一下ELF文件格式。

ELF文件格式

编译器编译源代码后生成的文件叫做目标文件,而目标文件经过编译器链接之后得到的就是可执行文件。那么目标文件到底是什么?

目前,PC平台流行的 可执行文件格式(Executable) 主要包含如下两种,它们都是 COFF(Common File Format) 格式的变种。

  • Windows下的 PE(Portable Executable)
  • Linux下的 ELF(Executable Linkable Format)

目标文件就是源代码经过编译后但未进行连接的那些中间文件(Windows的.obj和Linux的.o),它与可执行文件的格式非常相似,所以一般跟可执行文件格式一起采用同一种格式存储。在Windows下采用PE-COFF文件格式;Linux下采用ELF文件格式。

事实上,除了可执行文件外,动态链接库(DDL,Dynamic Linking Library)静态链接库(Static Linking Library) 均采用可执行文件格式存储。它们在Window下均按照PE-COFF格式存储;Linux下均按照ELF格式存储。只是文件名后缀不同而已。

总结来说,ELF文件格式包括三种主要的类型:可执行文件、可重定向文件、共享库。

1.可执行文件(应用程序)

可执行文件包含了代码和数据,是可以直接运行的程序。

2.可重定向文件(*.o)

可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。

.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些.o文件的活动可以反映出不同的需要。

Linux下,我们可以用gcc -c编译源文件时可将其编译成*.o格式。

3.共享文件(*.so)

也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为ld.so.1,libc.so.1或者 ld-linux.so.1。我的CentOS6.0系统中该文件为:/lib/ld-2.12.so

那么,什么是库呢?

库是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。

静态库

Windows:.lib 格式

Linux:.a 格式

静态库是在汇编过程生成的,加载静态态库是在链接过程之前。静态库实际就是一些目标文件(一般以.o结尾)的集合,静态库一般以.a结尾,只用于生成可执行文件阶段。

在链接步骤中,链接器将从库文件取得所需代码,复制到生成的可执行文件中。这种库成为静态库。

可执行文件中包含了库代码的一份完整拷贝,在编译过程中被载入程序中。

缺点就是多次使用就会有多份冗余拷贝,并且程序的更新、部署和发布很麻烦,如果静态库有更新,那么所有使用它的程序都需要重新编译、发布。

生成静态库的过程

  1. 将源文件生成 目标文件(完成到汇编的过程)
$ gcc -c test.c -o test.o
  1. 使用ar命令将test.o打包成libtest.a静态库
$ ar rcs libtest.a test.o
  1. 查看静态库的具体内容, 静态库其实就是目标文件的集合
$ ar t libtest.a 
test.o

动态库

Windows:.dll 格式

Linux:.so 格式

动态库是在汇编过程生成的,加载动态库是在程序运行过程中

动态库和静态库类似,但是它并不在链接时将需要的二进制代码都“拷贝”到可执行文件中,而是仅仅“拷贝”一些重定位和符号表信息,这些信息可以在程序运行时完成真正的链接过程。也就是说,动态库在链接阶段没有被复制到程序中,而是在程序运行时由系统动态加载到内存中供程序调用。

系统只需要载入一次动态库,不同的程序可以得到内存中相同动态库的副本,因此节省了很多内存。

程序运行可执行文件加载动态库,需要动态的设置动态库地址,才能运行。即将动态库放置在可执行文件同级目录下。

生成动态库的过程

首先生成test.o目标文件。

$ gcc -c test.c 

使用-share和-fPIC参数生成动态库。

$ gcc -shared -fPIC -o libtest.so test.o

编译可执行文件并链接动态库

$ gcc -o main main.c -L. -ltest

此时生成的可执行文件无法直接使用,需要动态设置动态库地址。

$ LD_LIBRARY_PATH. ./main

把动态库地址设置到当前目录下面即可。

动态库与静态库的区别

可执行文件大小不一样

从前面也可以观察到,静态链接的可执行文件要比动态链接的可执行文件要大得多,因为它将需要用到的代码从二进制文件中“拷贝”了一份,而动态库仅仅是复制了一些重定位和符号表信息。

占用磁盘大小不一样

如果有多个可执行文件,那么静态库中的同一个函数的代码就会被复制多份,而动态库只有一份,因此使用静态库占用的磁盘空间相对比动态库要大。

扩展性与兼容性不一样

如果静态库中某个函数的实现变了,那么可执行文件必须重新编译,而对于动态链接生成的可执行文件,只需要更新动态库本身即可,不需要重新编译可执行文件。正因如此,使用动态库的程序方便升级和部署。

依赖不一样

静态链接的可执行文件不需要依赖其他的内容即可运行,而动态链接的可执行文件必须依赖动态库的存在。所以如果你在安装一些软件的时候,提示某个动态库不存在的时候也就不奇怪了。

即便如此,系统中一班存在一些大量公用的库,所以使用动态库并不会有什么问题。

复杂性不一样

相对来讲,动态库的处理要比静态库要复杂,例如,如何在运行时确定地址?多个进程如何共享一个动态库?当然,作为调用者我们不需要关注。另外动态库版本的管理也是一项技术活。这也不在本文的讨论范围。

加载速度不一样

由于静态库在链接时就和可执行文件在一块了,而动态库在加载或者运行时才链接,因此,对于同样的程序,静态链接的要比动态链接加载更快。所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的。再加上局部性原理,牺牲的性能并不多。

agedcat_xuanzai

这个人很懒,什么都没留下

文章评论