linux下C++ 编译器、链接器、目标文件、库文件与链接参数
C++ 工程化编译入门:编译器、链接器、目标文件、库文件与链接参数
今天主要学习的是 C++ 工程里从源码到可执行程序的底层过程。
重点不是语法,而是:
- 编译器在干什么
- 链接器在干什么
.o和.a是什么关系-I、-L、-l分别是什么意思- 静态库和动态库怎么参与链接
一、C++ 程序不是一步直接变成可执行文件
平时写:
g++ main.cpp src/swap.cpp -Iinclude -o main
看起来像一步完成,但实际上里面大概分成两个阶段:
- 编译阶段:
.cpp -> .o - 链接阶段:
.o / 库文件 -> 可执行程序
也就是说:
编译器负责把源码变成目标文件。
链接器负责把多个目标文件或库文件拼成最终程序。
二、编译器做的事情
编译器主要处理单个源文件。
例如:
g++ main.cpp -c -Iinclude
这里:
-c 只编译,不链接
-Iinclude 指定头文件搜索目录
执行后生成:
main.o
再比如:
g++ src/swap.cpp -c -Iinclude
生成:
swap.o
.o 文件可以理解成:
已经编译好的机器码零件。
但是它还不是完整程序。
三、链接器做的事情
链接器负责把这些“零件”拼起来。
例如:
g++ main.o swap.o -o main
意思是:
把
main.o和swap.o链接起来,生成可执行程序main。
如果 main.o 里调用了某个函数,但是链接器没有在其他 .o 或库文件里找到它的实现,就会报:
undefined reference to `xxx`
这个错误的核心意思是:
编译阶段认识这个函数,但是链接阶段找不到它真正的实现。
所以:
undefined reference一般不是头文件问题,而是链接问题。
四、.o 和 .a 不一样
今天比较容易混的是:
.o文件是不是静态库?
答案是:
.o不是静态库,.a才是静态库。
它们的关系是:
swap.cpp -> swap.o -> libswap.a
其中:
swap.o 目标文件
libswap.a 静态库
.a 本质上就是把一个或多个 .o 打包起来。
例如:
ar rs libswap.a swap.o
意思是:
把
swap.o打包成静态库libswap.a。
五、如果已经有 .o 文件,还需要 -Lsrc -lswap 吗?
不需要。
如果已经有:
src/swap.o
可以直接链接:
g++ main.cpp -Iinclude src/swap.o -o main
这里已经直接把 swap.o 的路径告诉 g++ 了,所以不需要:
-Lsrc -lswap
因为 -L 和 -l 是“按库名搜索库文件”的写法。
而现在是直接指定了目标文件路径。
六、如果已经有 .a 静态库,还需要 -Lsrc -lswap 吗?
不一定。
如果已经有:
src/libswap.a
可以直接写:
g++ main.cpp -Iinclude src/libswap.a -o static_main
这表示:
- 编译
main.cpp - 头文件去
include目录找 - 直接把
src/libswap.a链接进来 - 生成
static_main
这种写法最直观。
但是也可以用更工程化的写法:
g++ main.cpp -Iinclude -Lsrc -lswap -o static_main
两种写法都可以。
区别是:
| 写法 | 含义 |
|---|---|
src/libswap.a |
直接指定库文件路径 |
-Lsrc -lswap |
指定库目录和库名,让链接器去找 |
七、-Iinclude 是什么?
-Iinclude 里的 I 是大写字母 I,意思是:
include search path
也就是:
头文件搜索路径。
它是给编译器看的。
例如:
g++ main.cpp -Iinclude -o main
意思是:
编译
main.cpp时,如果遇到#include "xxx.h",就去include目录里找。
所以:
-I 是编译阶段用的
-I 后面跟的是头文件目录
注意:
-I不是找库文件的。
八、-Lsrc 是什么?
-Lsrc 里的 L 是大写字母 L,意思是:
library search path
也就是:
库文件搜索路径。
它是给链接器看的。
例如:
g++ main.cpp -Iinclude -Lsrc -lswap -o static_main
其中:
-Lsrc
意思是:
链接时去
src目录里面找库文件。
所以:
-L 是链接阶段用的
-L 后面跟的是库所在目录
它只告诉链接器“去哪里找”,但还没有说“找哪个库”。
九、-lswap 是什么?
-lswap 里的 l 是小写字母 l,意思是:
link library
也就是:
链接某个库。
它告诉链接器:
我要链接
swap这个库。
但是它真正找的文件不是:
swap.a
而是按照规则去找:
libswap.a
libswap.so
规则是:
-lxxx -> libxxx.a 或 libxxx.so
所以:
-lswap
对应的是:
libswap.a
libswap.so
不能写成:
-llibswap
因为这样会变成找:
liblibswap.a
liblibswap.so
这是今天非常容易混的地方。
十、-I、-L、-l 三个参数的区别
这三个参数看起来像,但完全不是一回事。
| 参数 | 阶段 | 作用 |
|---|---|---|
-Iinclude |
编译阶段 | 指定头文件搜索目录 |
-Lsrc |
链接阶段 | 指定库文件搜索目录 |
-lswap |
链接阶段 | 指定要链接的库名 |
例如:
g++ main.cpp -Iinclude -Lsrc -lswap -o static_main
可以拆成:
| 部分 | 含义 |
|---|---|
main.cpp |
要编译的主程序源文件 |
-Iinclude |
编译阶段去 include 找头文件 |
-Lsrc |
链接阶段去 src 找库文件 |
-lswap |
链接 libswap.a 或 libswap.so |
-o static_main |
输出可执行程序 static_main |
一句话理解:
-I是找声明需要的目录。
-L是找库所在的目录。
-l是找具体哪个库。
十一、静态库的链接
静态库后缀是:
.a
比如:
libswap.a
生成静态库:
cd src
g++ swap.cpp -c -I../include
ar rs libswap.a swap.o
回到项目根目录链接:
cd ..
g++ main.cpp -Iinclude -Lsrc -lswap -o static_main
也可以直接写库文件路径:
g++ main.cpp -Iinclude src/libswap.a -o static_main
静态库的特点:
链接时需要 libswap.a
运行时不需要 libswap.a
库里的代码会被放进最终可执行程序
所以生成 static_main 后,即使不带着 libswap.a,程序也能运行。
十二、动态库的生成和链接
动态库后缀是:
.so
比如:
libswap.so
生成动态库:
g++ swap.cpp -I../include -fPIC -shared -o libswap.so
参数解释:
| 参数 | 含义 |
|---|---|
swap.cpp |
要编译的源文件 |
-I../include |
找头文件目录 |
-fPIC |
生成位置无关代码,动态库常用 |
-shared |
生成动态库 |
-o libswap.so |
输出动态库文件 |
其中:
-fPIC
表示生成位置无关代码。
动态库运行时可能被加载到不同内存位置,所以通常要加这个参数。
-shared
表示生成共享库,也就是动态库。
如果不加它,g++ 默认会尝试生成可执行文件。
十三、动态库怎么和 main.cpp 链接?
假设动态库在:
src/libswap.so
链接主程序:
g++ main.cpp -Iinclude -Lsrc -lswap -o dyna_main
这一步和静态库很像:
| 参数 | 含义 |
|---|---|
-Iinclude |
找头文件 |
-Lsrc |
找库目录 |
-lswap |
找 libswap.so 或 libswap.a |
如果 src 目录下同时有:
libswap.a
libswap.so
通常会优先链接动态库:
libswap.so
也可以直接写动态库路径:
g++ main.cpp -Iinclude src/libswap.so -o dyna_main
十四、动态库运行时还要找 .so
动态库和静态库最大的区别在运行阶段。
静态库:
链接时需要 .a
运行时不需要 .a
动态库:
链接时需要 .so
运行时也需要找到 .so
所以如果直接运行:
./dyna_main
可能会报:
error while loading shared libraries: libswap.so: cannot open shared object file
原因是:
链接时
-Lsrc让链接器找到了libswap.so,但是运行时 Linux 系统不一定知道去src目录找libswap.so。
可以临时这样运行:
LD_LIBRARY_PATH=./src ./dyna_main
意思是:
本次运行时,把
./src加入动态库搜索路径。
十五、今天几个疑惑的总结
疑惑一:.o 是不是静态库?
不是。
.o 是目标文件
.a 是静态库
.a 是由 .o 打包出来的。
疑惑二:已经有 .o 了,还要 -Lsrc -lswap 吗?
不需要。
直接写:
g++ main.cpp -Iinclude src/swap.o -o main
疑惑三:已经有 .a 了,还要 -Lsrc -lswap 吗?
不一定。
可以直接写:
g++ main.cpp -Iinclude src/libswap.a -o static_main
也可以写:
g++ main.cpp -Iinclude -Lsrc -lswap -o static_main
前者是直接指定库文件,后者是让链接器按规则搜索库文件。
疑惑四:-lswap 为什么不是找 swap.a?
因为 -lxxx 有固定规则:
-lxxx -> libxxx.a 或 libxxx.so
所以:
-lswap
找的是:
libswap.a
libswap.so
不是:
swap.a
疑惑五:为什么不能写 -llibswap?
因为:
-llibswap
会被理解成:
liblibswap.a 或 liblibswap.so
所以如果库文件叫:
libswap.a
应该写:
-lswap
疑惑六:-Iinclude 和 -Lsrc 有什么区别?
-Iinclude 找头文件目录,编译阶段用
-Lsrc 找库文件目录,链接阶段用
一个给编译器看,一个给链接器看。
十六、今天最核心的理解
C++ 工程构建可以压缩成一句话:
编译器把
.cpp变成.o;
链接器把.o、.a、.so这些实现拼成最终可执行程序。
参数可以这样记:
-I:找头文件目录
-L:找库文件目录
-l:找具体库名
.o 和 .a 的关系:
.o 是单个目标文件
.a 是静态库,本质上是一组 .o 的打包
静态库和动态库的区别:
静态库 .a:链接时用,运行时不需要
动态库 .so:链接时用,运行时也要能找到
今天算是把 C++ 从“单文件编译”推进到了“工程化编译链接”的第一步。

浙公网安备 33010602011771号