动态库 vs 静态库

前言

我们经常在程序中用函数来抽象表达可被复用的逻辑以节省时间。在程序运行时,库进一步拓展了这个优点,因为它们使函数可以在多个程序中重用。

静态库 (Static libraries) 可以被多个程序复用,但会被集成到程序之中。然而,动态库或者说共享库(Dynamic/Shared libraries) ,是可执行程序之外单独存在的文件。

缺点

静态库是作为可执行程序的一部分而存在的,若要修改库中的代码就必须要重新编译,而动态库则避免了这样的麻烦。

动态库的缺点从一个不少人都可能遇到过的错误就能看出来:“缺少 xxx.dll,重新安装此程序或解决问题”,也就是说如果 dll 文件缺失或者损坏,程序是无法运行的,从这一点上来说,程序的不稳定性提高了。而静态库由于被集成到了可执行文件里所以不存在这样的问题。

优点

使用静态库可以提高程序运行时的速度,因为二进制代码已经包含在程序中了,多次的函数调用可以比使用动态库来的更快,因为后者需要从可执行文件之外调用。

动态库的好处是可以让多个运行中的程序使用同一个库而不是各自需要拥有一个库的拷贝。

在实际的产品迭代中,若你要给很多的用户推送更新,你是希望只推送那些被更改的库文件还是整个程序集呢。我想,答案不是固定的,这取决你的侧重点。若程序体积本身就比较小,而且更倾向于性能,可以选择用静态库;若程序的依赖文件很多,再使用静态库会让可执行文件体积变大,推送更新时的性价比就不高了,这时可以考虑动态库。

下面在 Linux 环境中举例如何创建这二者:

创建动态库

  • gcc *.c -c -fpic

这条命令会让 *.c 源文件被编译成动态库。因为多个程序可以都使用一个动态库,所以库在内存中的地址是在程序之间变换的,需要加上 -fpic 这个编译器选项。由于需要在编译时生成目标代码后应用此步骤,因此必须告诉编译器暂停并为每个源文件返回一个目标文件 *.o。 这是通过使用 -c 标志来完成的。

  • gcc *.o -shared -o liball.so

现在这些对象文件已经准备好被编译成一个动态库了,这是通过编译所有的 *.o 文件并且加上 -shared 选项完成的。稍后在编译程序文件时,编译器通过查找以 lib 开头并以库扩展名结尾的文件 (.so 表示动态库,.a 表示静态库) 来识别库。 因此,相应地命名库很重要。

  • export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH

因为一个程序需要知道哪里可以找到库文件,所以我们必须要在环境变量 LD_LIBRARY_PATH 后加上那个位置。

创建静态库

创建静态库相对来说要简单一些。首先和上面第一步一样创建对象文件,然后通过 ar rcs liball.a *.0 打包成库。另外,程序还必须包括库中所有函数的原型,如果你是通过头文件完成的,记得要在使用这个库的其他文件中 #include <header file>

编译程序

  • gcc -L. -lall -o main main.c

当编译程序时,我们需要让编译器使用库文件并告诉其位置。-l 表明我们要包含库文件。all 表明要寻找 liball.so 这个库。值得注意的是,我们要去掉 lib.so ,因为编译器已经以这种方式识别了库文件。-L. 告诉编译器可以在当前目录下找到库文件

版本控制

Version Action Time
1.0 Init 2021-07-15
1.1 Revised the wording 2021-07-16