linux下C++ 编译器、链接器、目标文件、库文件与链接参数

C++ 工程化编译入门:编译器、链接器、目标文件、库文件与链接参数

今天主要学习的是 C++ 工程里从源码到可执行程序的底层过程。

重点不是语法,而是:

  • 编译器在干什么
  • 链接器在干什么
  • .o.a 是什么关系
  • -I-L-l 分别是什么意思
  • 静态库和动态库怎么参与链接

一、C++ 程序不是一步直接变成可执行文件

平时写:

g++ main.cpp src/swap.cpp -Iinclude -o main

看起来像一步完成,但实际上里面大概分成两个阶段:

  1. 编译阶段:.cpp -> .o
  2. 链接阶段:.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.oswap.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.alibswap.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.solibswap.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++ 从“单文件编译”推进到了“工程化编译链接”的第一步。

posted @ 2026-06-21 15:09  dtcoke  阅读(10)  评论(0)    收藏  举报