阅读本文大概需要 8 分钟。
静态库和动态库
静态库以.a文件结尾, 是目标文件(.o)的集合
C语言运行库glibc中包含了很多跟系统调用相关的代码,比如输入输出、文件操作、时间管理、内存管理等。glibc本身使用c语言开发的开发的,由上千个c语言源文件组成,编译后有相同数量的目标文件,比如输入输出有printf.o,scans.o,文件操作有fread.o,fwrite.o。如果把这些零散的文件直接提供给使用者,会造成文件传输、组织和管理的不便。把这些目标文件压缩在一起,对其进行编号和索引,以便查找和检索,方便使用,这便形成了libc.a
# 目标文件 -> 静态库
ar rcs libxxx.a fread.o fwrite.o
静态库链接时,链接器(linker)会将静态库中被用到的.o文件写入到最终的应用程序中
链接静态库生成的应用程序体积比较大; 静态库链接限制了程序灵活更新
动态库以.so文件结尾
[root@3f43043144d6 app]# ldd /bin/pwd
linux-vdso.so.1 => (0x00007fffd7c10000)
libc.so.6 => /lib64/libc.so.6 (0x00007efcc0d24000)
/lib64/ld-linux-x86-64.so.2 (0x00007efcc10f2000)
# 源文件 -> 动态库
gcc -fPIC -shared xxx1.c xxx2.c -o libxxx.so
# 目标文件 -> 动态库
gcc -fPIC -shared xxx1.o xxx2.o -o libxxx.so
①头文件搜索路径、②编译时库文件搜索路径、③运行时库文件搜索路径
程序编译时也可以指定头文件和库文件目录,gcc会到指定的目录下查找
程序运行时也可以指定动态库的搜索路径,加载器会到指定的目录下查找要链接的动态库
可用 cc1plus 命令查看
[root@3f43043144d6 gcc-10.2.0]# /opt/compiler/gcc-10.2.0/libexec/gcc/x86_64-pc-linux-gnu/10.2.0/cc1plus -v
ignoring nonexistent directory "/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0/x86_64-pc-linux-gnu
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../include/c++/10.2.0/backward
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include
/usr/local/include
/opt/compiler/gcc-10.2.0/include
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/include-fixed
/usr/include
End of search list.
// 1. 在gcc命令中通过-I参数指定头文件搜索路径
gcc -I/opt/compiler/gcc-10.2.0/include -lstdc++ main.cpp -o main
// 2. 通过指定的环境变量来搜索
增进环境变量C_INCLUDE_PATH/CPLUS_INCLUDE_PATH/OBJC_INCLUDE_PATH指定的路径
// 3. 对开源软件而言,编译脚本configure一般都支持设置CPPFLASGS环境变量,指定额外的头文件搜索路径
./configure --prefix=/usr/local/krb5 CPPFLAGS="-I/usr/local/ldap/include"
多个头文件搜索路径和代码中include文件组合成多个候选的头文件路径
a/b/c/include
d/e/f/include
#include "a.h"
#include "include/b.h"
a/b/c/include/a.h
d/e/f/include/a.h
a/b/c/include/include/b.h
d/e/f/include/include/b.h
gcc按搜索路径的先后顺序遍历查找,找到第一个存在的文件路径便停止查找;头文件搜索目录多便会影响搜索的速度,进而影响编译速度
库文件搜索路径比较特殊,分为编译时和运行时两种,两种路径彼此独立互不干扰。运行时的搜索路径仅与动态库有关,静态库在编译阶段已经被全部链接到应用程序中,运行时不需要再链接
使用命令gcc -print-search-dirs 可以查看gcc默认的库文件搜索路径
[root@3f43043144d6 app]# gcc -print-search-dirs | grep libraries | awk -F'=' '{print $2}' | awk -F ':' '{for(i=1;i<=NF;i++) {print $i}}'
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/lib/x86_64-pc-linux-gnu/10.2.0/
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/lib/../lib64/
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../x86_64-pc-linux-gnu/10.2.0/
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../lib64/
/lib/x86_64-pc-linux-gnu/10.2.0/
/lib/../lib64/
/usr/lib/x86_64-pc-linux-gnu/10.2.0/
/usr/lib/../lib64/
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../../x86_64-pc-linux-gnu/lib/
/opt/compiler/gcc-10.2.0/lib/gcc/x86_64-pc-linux-gnu/10.2.0/../../../
/lib/
/usr/lib/
当程序依赖的库文件不在默认的库文件搜索路径下,就需要额外指定
# 在gcc中可以通过-L参数指定库文件搜索路径,通过-l参数指定库文件
gcc -o hello hello.cpp -L /usr/local/libxml/lib -lxml
# 编译期间增加库文件搜索路径
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/libxml/lib
# 在开源软件中,编译脚本configure 一般会支持设置环境变量LDFLAGS,添加库文件搜索路径
configure --prefix=xxxx LDFLAGS="-L/usr/local/libxml/lib -lxml"
对动态库而言,在编译阶段链接器(linker)并不会执行真正的链接动作,只是检查代码中所需的符号(函数、变量等)能否在动态库中找到,一旦找到便将库文件的元信息记录到应用程序中,直到所有的符号都被找到,相关的动态库信息都被记录到了应用程序中,链接动作可以便完成了,真正的链接动作在程序运行时由动态加载器(loader)来进一步完成
编译时依赖了动态库,启动时需要先加载动态库完成链接操作; 编译时指定的库文件搜索路径与运行时的库文件搜索路径没有任何关系
操作系统动态库搜索路径先后顺序:
①LD_RELOAD -> ②RPATH --> ③LD_LIBRARY_PATH
预加载库拥有最高的搜索优先级,加载器loader首先加载这个库
export LD_PRELOAD=/usr/local/libxml/lib:$LD_PRELOAD
LD_RUN_PATH是RPATH对应的环境变量,用于指定程序启动时动态库的加载路径,既可以在编译阶段指定,也可以在运行阶段指定。我们推荐在编译阶段指定,可以减少服务运维的难度和成本。
编译中指定:
// 编译过程中指定,只影响该文件的rpath
gcc -Wl,-R/usr/local/libxml/lib -lxml
运行中指定:
export LD_RUN_PATH=/usr/local/libxml/lib:$LD_RUN_PATH
LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径
有两种方式能够增加动态库的搜索路径
「方法一」 使用export修改
export LD_LIBRARY_PATH=/usr/local/libxml/lib:$LD_LIBRARY_PATH
最佳实践是修改LD_LIBRARY_PATH之前,备份旧的LD_LIBRARY_PATH,便于修改后还原
# 记录旧的LD_LIBRARY_PATH
export OLD_LD_LIBRARY_PATH=$LD_LIBRARY_PATH
# 设置新的LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/home/work/project/libs:$LD_LIBRARY_PATH
# 还原旧的LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$OLD_LD_LIBRARY_PATH
「方法二」 使用动态装入器修改
动态装入器负责将动态可执行程序和所有必需的共享库一起装入,以使它们能正确执行;动态装入器找到共享库要依靠两个文件:/etc/ld.so.conf 和 /etc/ld.so.cache
动态装入器在寻找共享库时会通过 /etc/ld.so.cache 文件查看在 /etc/ld.so.conf 中指定的所有新目录
# 1. vim /etc/ld.so.conf 添加目录
include ld.so.conf.d/*.conf
/opt/compiler/gmp-6.1.0/lib
/opt/compiler/isl-0.18/lib
/opt/compiler/mpc-1.0.3/lib
/opt/compiler/mpfr-3.1.4/lib
# 2. 生成cache文件
ldconfig
# 3. 查看cache文件内容
strings /etc/ld.so.cache
往期推荐
【c++连载】科普编译过程
【c++连载】搭建编译环境
【面试】彻底理解 IO多路复用
10分钟彻底理解自适应大邻域搜索算法
公众号后台回复「编程」送你编程面试资料