Makefile自动化构建
makemake。
·
一、Makefile 自动化构建基础
1. Makefile 的作用
- 自动化管理项目编译流程,通过 “目标 - 依赖 - 命令” 规则,让
make
工具自动判断文件是否需要重新编译,避免手动重复执行编译命令。 - 示例:简单的 Makefile 可仅包含一行目标与编译命令,执行
make
即可生成可执行文件。
2. Makefile 核心规则格式
makefile
目标文件: 依赖文件列表
命令(必须以 Tab 开头)
- 目标文件:要生成的文件(如可执行文件、目标文件
.o
等)。 - 依赖文件列表:生成目标文件所需的源文件 / 中间文件。
- 命令:生成目标文件的具体操作(如
gcc
编译命令),必须以 Tab 键开头(否则make
会报missing separator
错误)。
二、编译流程与依赖追溯(栈式推导)
1. C 程序编译的四个阶段
从源文件 *.c
到可执行文件,经历预处理、编译、汇编、链接四个阶段,对应 gcc
不同参数:
阶段 | 命令 | 作用 | 输出文件 |
---|---|---|---|
预处理 | gcc -E test.c -o test.i |
处理宏、头文件包含、注释等,生成预处理后的代码。 | .i |
编译 | gcc -S test.i -o test.s |
将预处理代码编译为汇编代码。 | .s |
汇编 | gcc -c test.s -o test.o |
将汇编代码转换为二进制目标文件(不可直接执行)。 | .o |
链接 | gcc test.o -o test.exe |
将目标文件与系统库链接,生成可执行文件。 | test.exe |
2. Makefile 依赖的 “栈式追溯”
make
工具解析依赖时,会从最终目标反向推导,过程类似 “栈的入栈 / 出栈”:
- 入栈(依赖追溯):要生成
test.exe
,先检查依赖test.o
;接着追溯test.o
的依赖test.s
,再追溯test.s
的依赖test.i
,最后追溯test.i
的依赖test.c
。 - 出栈(命令执行):执行顺序与追溯顺序相反,从最底层的
test.c
开始,依次执行 “预处理→编译→汇编→链接”,最终生成test.exe
。
三、Makefile 进阶:伪目标与文件时间戳
1. 伪目标(.PHONY)
- 作用:当需要执行 “非生成文件” 的操作(如
clean
清理中间文件)时,避免与同名文件冲突。 - 定义方式:
makefile
.PHONY: clean # 声明 clean 为伪目标 clean: rm -f test.exe test.i test.s test.o # 清理所有中间文件和可执行文件
- 特点:伪目标总是会被执行(不受文件时间戳影响);
make
默认只执行第一个目标,若clean
不是第一个目标,需显式执行make clean
。
2. 文件时间戳与增量编译
make
的核心机制:通过比较目标文件与依赖文件的修改时间(mtime),判断是否需要重新编译。- 若
test.c
的修改时间晚于test.o
,make
会重新编译test.c
生成新的test.o
,再链接生成test.exe
。 - 若所有依赖的修改时间都早于目标文件,则跳过编译。
- 若
- 验证工具:
stat
命令,可查看文件的Access
(访问时间)、Modify
(修改时间)、Change
(属性变更时间),示例:bash
stat test.c # 查看 test.c 的时间戳信息
四、静态库与动态库(链接相关)
1. 静态库(Static Library)
- 特点:编译时将库代码直接嵌入可执行文件,生成的程序不依赖外部库、可独立运行,但文件体积较大。
- 操作流程:
- 生成:
ar rcs libxxx.a xxx.o
(将目标文件xxx.o
归档为静态库libxxx.a
)。 - 链接:
gcc main.c -o main -L. -lxxx
(-L
指定库路径,-l
指定库名,库名需去掉lib
和.a
后缀)。
- 生成:
- 系统安装:
- Ubuntu:静态库通常包含在
-dev
包中(如zlib1g-dev
),可通过sudo apt install 包名
安装。 - CentOS:可通过
yum install glibc-static
等命令直接安装系统提供的静态库。
- Ubuntu:静态库通常包含在
2. 动态库(Shared Library)
- 特点:程序运行时才加载库到内存,多个程序可共享同一份库,可执行文件体积小,但依赖系统中存在对应动态库。
- 操作流程:
- 生成(Linux):
gcc -shared -fPIC -o libxxx.so xxx.c
(-fPIC
生成 “位置无关代码”,保证库在内存中可共享)。 - 链接:
gcc main.c -o main -L. -lxxx
(与静态库链接命令类似,但运行时需确保LD_LIBRARY_PATH
包含库路径)。
- 生成(Linux):
- 依赖查看:
ldd 可执行文件
(查看程序依赖的动态库,如ldd test.exe
)。
五、程序执行与标准 I/O
1. 标准流与输出重定向
- C 程序中,
printf
输出到标准输出(stdout),scanf
从标准输入(stdin)读取,错误信息输出到标准错误(stderr)。 - 示例:
- 终端显示:
./test
(直接执行程序,输出到终端)。 - 重定向输出:
./test > output.txt
(将stdout
内容写入output.txt
)。
- 终端显示:
2. 程序退出与 I/O 缓存
- 程序正常退出:
return 0
或exit(0)
;非 0 值表示异常退出。 - I/O 缓存问题:标准 I/O 有缓冲区,若程序突然终止(如崩溃),缓冲区未刷新的内容可能无法输出。解决方法:
- 强制刷新:
fflush(stdout)
。 - 关闭缓冲:
setbuf(stdout, NULL)
。
- 强制刷新:
补充
1.为何除BIN和SRC外还要有OBJ?
2.$^和$<的区别:
3.如果OBJ文件中有.txt文件时,还能用OBJ=$(SRC:.c=.o)吗?
更多推荐
所有评论(0)