一、编译简介
项目编译都经历了两个阶段:编译、链接
- 无论项目多么庞大, 编译都是单文件编译,且编译时不用管函数实现,只需知道函数定义即可,所以编译要找到对应的头文件。(编译时用00占位符,表示函数地址)
- 链接才支持多文件输入, 这阶段会修正函数地址,将上面的00修改为函数的真正地址。静态库链接不是将静态库的内容全部复制,只有用到才会复制。
库类型:
- 静态库:是一个归档文件,即打包文件。可以对其进行解压,解压后的.o文件再和自己功能.o文件,重新打包成新的静态库。静态库里面的函数地址还是00,所以打包静态库不涉及链接操作(即编译+打包)
- 动态库:函数地址不是00,但也不是真正的函数地址,是符号表的地址,动态库是通过符号表跳转到函数真正地址。(Linux的动态库是将函数符号信息和函数实现放在一个文件,而Windows的MSVC是分开两个文件dll和import lib)
二、头文件和依赖库搜索路径
2.1 头文件
(1)默认搜索路径
编译器(如gcc、clang)会按照如下顺序搜索头文件:
- 当前目录:对与使用双引号
#include "header.h"
的情况,编译器会首先在源文件所在目录查找。 - 标准系统路径:对于使用尖括号
#include <header.h>
的情况,编译器会查找系统默认路径,通常包括:/usr/include
/usr/local/include
- 编译器内置路径(如
/usr/lib/gcc/x86_64-linux-gnu/<version>/include
)
(2)自定义搜索路径
通过编译器选项 指定头文件搜索路径:
I<路径>
:指定头文件搜索的目录,优先级高于系统默认路径- 示例:
gcc -I./include main.c
- 示例:
-isystem <路径>
:指定系统头文件路径,优先级低于 -I 但高于标准路径- 环境变量:某些编译器会参考环境变量
CPATH
或C_INCLUDE_PATH
(C 程序)或CPLUS_INCLUDE_PATH
(C++ 程序)来查找头文件。- 示例:
export C_INCLUDE_PATH=/path/to/include
- 示例:
2.2 库文件
2.2.1 链接阶段
(1)默认搜索路径
链接器通常会搜索以下标准路径:
/lib
/usr/lib
/usr/local/lib
- 编译器特定的库路径(如
/usr/lib/gcc/x86_64-linux-gnu/<version>
)
(2)自定义搜索路径
通过编译器选项 指定库搜索路径:
-L<路径>
:在链接时指定库文件的路径,优先级高于系统默认路径- 示例:
gcc main.c -L./libs -lmylib
- 示例:
- 环境变量
LIBRARY_PATH
:链接器会参考LIBRARY_PATH
环境变量指定的路径。- 示例:
export LIBRARY_PATH=/path/to/libs
- 示例:
- 使用
-l<库名>
指定要链接的库,链接器会自动在搜索路径中查找lib<库名>.a
或lib<库名>.so
- 示例:
-lm
表示链接libm.so
或libm.a
(数学库)。
- 示例:
2.2.2 运行阶段(动态链接)
- 程序中指定的 RPATH:编译时通过
-rpath
或--rpath
选项嵌入到可执行文件中的路径。- 示例:
gcc -Wl,-rpath,/path/to/libs main.c
- 示例:
- LD_LIBRARY_PATH 环境变量:运行时指定的动态库路径,优先级高于系统默认路径。
- 示例:
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
- 示例:
- /etc/ld.so.cache:由
ldconfig
工具生成,缓存了系统默认路径中的库信息。- 通常包括
/lib
、/usr/lib
、/usr/local/lib
等 - 将
/etc/ld.so.conf
或/etc/ld.so.conf.d/*.conf
中配置的路径加入缓存 - 查看当前动态库缓存:
ldconfig -p
- 更新动态库缓存:
sudo ldconfig
- 通常包括
- 系统默认路径:如
/lib
、/usr/lib
2.3 常见问题与解决方法
- 头文件未找到:
- 检查头文件路径是否正确,使用
-I
指定路径。 - 确保头文件与源代码的
#include
语法匹配(双引号或尖括号)。
- 检查头文件路径是否正确,使用
- 库文件未找到(链接时):
- 使用
-L
指定库路径,检查库文件是否存在。 - 确保
-l
指定的库名正确(去掉lib
前缀和.a
/.so
后缀)。
- 使用
- 动态库未找到(运行时):
- 检查
LD_LIBRARY_PATH
或RPATH
设置。 - 确保动态库已安装并在
ldconfig
缓存中。 - 使用
ldd
检查依赖是否正确解析。
- 检查
三、如何指定静态链接和动态链接
3.1 静态链接
静态链接是指在编译时将库的代码直接嵌入到生成的可执行文件中,生成的可执行文件包含了所有需要的代码,运行时无需依赖外部库文件。
注意:不是将库的所有代码嵌入,而是程序用到的函数代码
常见方式:
- 使用
-static
选项,强制使用静态库链接- 示例:
gcc main.c -o myprogram -static # 会链接静态运行时库
- 示例:
- 用静态库全名
- 示例:
gcc main.c -L/path/to/lib -l:libexample.a -o myprogram
- 示例:
3.2 动态链接
动态链接是指在编译时仅在可执行文件中记录对动态库(.so 文件)的引用,运行时动态加载这些库。动态链接是大多数编译器的默认行为。
常见方式:
- 默认行为: GCC 和 Clang 默认使用动态链接,因此通常不需要额外指定。
- 显式指定动态库:使用
-l
选项指定动态库名称(去掉lib
前缀和.so
后缀)- 示例:
gcc main.c -L/path/to/lib -lexample -o myprogram
- 示例:
3.3 混合链接
在某些情况下,可以混合使用静态和动态链接。例如,强制链接某些库为静态,而其他库保持动态。可以使用 -Wl,-Bstatic
和 -Wl,-Bdynamic
选项来控制:
-Wl,-Bstatic
:接下来的-l
指定的库将以静态方式链接。-Wl,-Bdynamic
:接下来的-l
指定的库将以动态方式链接。
示例:
gcc -o program source.o -L/usr/local/lib -Wl,-Bstatic -lexample -Wl,-Bdynamic -lanother
libexample.a
将被静态链接。libanother.so
将被动态链接。