Makefile
文章目录
- Makefile
- 1.初次实现
-
- [1.1 没创建之前](#1.1 没创建之前)
- 1.2使用makefile
- [1.3 把make和clean调个位置------ 验证「默认目标」规则](#1.3 把make和clean调个位置—— 验证「默认目标」规则)
- [1.4 .PHONY ------让目标总是被执行](#1.4 .PHONY ——让目标总是被执行)
-
- 尝试多敲几次make
- [给 myproc 也加上 .PHONY](#给 myproc 也加上 .PHONY)
- 关键点
- 有关ACM时间
- 2.第二版Makefile
-
-
- [`gcc` 对照表](#
gcc对照表)
- [`gcc` 对照表](#
-
- 3.第三版Makefile
- 4.最终版Makefile
1.初次实现
1.1 没创建之前
cpp
mkdir lesson9
cd lesson9
vim myproc.c # 写入上面 代码,:wq 保存退出
进 vim 后写(i 进插入模式,写完 ESC 再 :wq 保存退出):
cpp
#include <stdio.h>
int main()
{
printf("hello bit!\n");
return 0;
}
这时候只要一串"gcc -o myproc myproc.c"就能让它进行编译
然后再来这一行"./myproc"就能够直接运行
可是感觉这样有点麻烦,所以我们实现Makefile
1.2使用makefile
Makefile的格式
cpp
目标文件: 依赖文件列表
<Tab>依赖方法 #前面必须是tab
创建第一个makefile
- 创建:
bash
vim Makefile # 新建并打开 Makefile
- 写入
makefile
myproc:myproc.c
gcc -o myproc myproc.c
- 退出写入之后运行
bash
make
gcc -o myproc myproc.c
ls
Makefile myproc myproc.c
实现clean
- 重新打开 Makefile
bash
vim Makefile # 重新打开 Makefile,接着往下写
- 写入Makefile
makefile
myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean # 修饰符,声明 clean 是伪目标
clean: # clean 也要有依赖关系,但依赖文件列表可以为空
rm -f myproc # 依赖方法:删掉编译产物
- 流程
bash
make # 自动执行gcc,编译生成myproc
gcc -o myproc myproc.c
make clean # 执行rm,删掉myproc
rm -f myproc
ls
Makefile myproc.c
clean的语法
一般Makefile的格式
cpp
目标文件: 依赖文件列表
<Tab>依赖方法 #前面必须是tab
| 点 | 说明 |
|---|---|
clean 的语法结构 |
和 myproc 一模一样:也是一组"依赖关系 + 依赖方法"。区别只是它的方法不形成文件,而是删文件 |
| 依赖列表为空 | clean: 后面什么都不写也合法 ------ 它不依赖任何文件 |
为什么 make clean 要带个名字 |
make 默认只做第一个目标(见块 4),clean 是第二个,得显式点名 |
1.3 把make和clean调个位置------ 验证「默认目标」规则
bash
vim Makefile
调换后的 Makefile:
makefile
.PHONY:clean
clean:
rm -f myproc
myproc:myproc.c
gcc -o myproc myproc.c
- 展示:
bash
make
rm -f myproc
make myproc
gcc -o myproc myproc.c
| 点 | 说明 |
|---|---|
| 默认目标 | make 从上往下 扫描 makefile,把第一个目标 当默认目标(.PHONY 这种以点开头的特殊目标会被跳过) |
| 只做第一个 | 第一个目标做完了,第二个不会 跟着做,除非你显式点名(make 目标名) |
| 结论 | 所以默认把可执行程序写在最前面。 |
1.4 .PHONY ------让目标总是被执行
尝试多敲几次make
bash
# 先清理干净,再连续敲四次 make,一次都别落
make clean
rm -f myproc
make
gcc -o myproc myproc.c
make
make: 'myproc' is up to date.
make
make: 'myproc' is up to date.
make
make: 'myproc' is up to date.
关键点
| 点 | 说明 |
|---|---|
| 现象 | 第 1 次真编;第 2 ~ 4 次只回一句 make: 'myproc' is up to date. |
up to date 什么意思 |
make 认为目标文件已经是最新的,没必要重新生成 |
给 myproc 也加上 .PHONY
打开 Makefile,在 myproc: 那一组的上面 加一行修饰符,然后 make 四次:
bash
vim Makefile
.PHONY:myproc # 加这一行:修饰下面的目标
myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean
clean:
rm -f myproc
make
gcc -o myproc myproc.c
make
gcc -o myproc myproc.c
make
gcc -o myproc myproc.c
make
gcc -o myproc myproc.c
关键点
| 问题 | 答案 |
|---|---|
| 结论 | .PHONY = 让 make 忽略源文件和可执行目标的 M 时间对比,从而让目标总是被执行 |
.PHONY 是什么 |
makefile 里的一种修饰符 ,被它修饰的目标叫伪目标 |
| 作用 | 目标总是被执行 ------ 也就是对应的依赖方法每次都跑 |
| 本质是什么 | make 调用 gcc 时忽略新旧时间对比 |
| 不给可执行程序加 | 一旦加了,就再也享受不到"源文件没改就不重编"的好处,多文件大项目会把时间全浪费在重复编译上 |
给 clean 加 |
清理每次都应该做彻底,后面清理可能做很多事,要保证它纯粹、干净,不会留下奇奇怪怪的中间产物 |
有关ACM时间
| 缩写 | 全称 | 含义 | 触发场景 |
|---|---|---|---|
| A | Access 访问时间 | 文件内容被读取的时间 | cat、vim 打开、程序读文件,只看内容不修改 |
| M | Modify 修改时间 | 文件内容数据被改动的时间 | echo、vim 保存写入,文件里面文字变了 |
| C | Change 状态变更时间 | 文件属性 / 元信息被改动的时间 | chmod 改权限、chown 改所有者、mv 改名、硬链接增减(inode 属性变,文件内容不变) |
查看时间状态的指令:
bash
stat myproc.c # 看源文件的三条时间
bash
stat myproc.c # 看源文件的三条时间
File: 'myproc.c'
Size: 359 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1321125 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ whb) Gid: ( 1000/ whb)
Access: 2024-10-23 18:51:20.000000000 +0800
Modify: 2024-10-23 18:51:20.000000000 +0800
Change: 2024-10-23 18:51:20.000000000 +0800
stat myproc # 看可执行文件的三条时间
File: 'myproc'
Size: 8440 Blocks: 24 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1321126 Links: 1
Access: (0775/-rwxrwxr-x) Uid: ( 1000/ whb) Gid: ( 1000/ whb)
Access: 2024-10-23 19:04:18.051190859 +0800
Modify: 2024-10-23 19:04:18.051190859 +0800
Change: 2024-10-23 19:04:18.051190859 +0800
有关touch改时间
- make识别重复的原理:通过modify时间
- touch 已存在文件,会改时间,touch完了之后又能make
| 用法 | 效果 |
|---|---|
touch 不存在的文件 |
新建一个空文件 |
touch 已存在的文件 |
把它的三条时间全部统一更新为现在(核心作用:change file timestamp) |
touch -a 文件 |
只改 access 时间 |
touch -m 文件 |
只改 modify 时间 |
touch -c 文件 |
不常用 ------ M 时间一改,属性一般也跟着改了 |
2.第二版Makefile
之前的Makefile(是一个简化版的版本):
makefile
myproc:myproc.c
gcc -o myproc myproc.c
详细版的是这样的:
makefile
myproc: myproc.o # 最终目标依赖目标文件
gcc myproc.o -o myproc # 链接:把 .o 变成可执行
myproc.o: myproc.s # .o 依赖汇编文件
gcc -c myproc.s -o myproc.o # 汇编:把 .s 变成 .o
myproc.s: myproc.i # .s 依赖预处理文件
gcc -S myproc.i -o myproc.s # 编译:把 .i 变成 .s
myproc.i: myproc.c # .i 依赖源文件
gcc -E myproc.c -o myproc.i # 预处理:把 .c 变成 .i
.PHONY:clean
clean:
rm -f *.i *.s *.o myproc
gcc 对照表
| 阶段 | 命令 | 输入 → 输出 | 干什么 |
|---|---|---|---|
| 预处理 | gcc -E myproc.c -o myproc.i |
.c → .i |
宏替换、去注释、条件编译、头文件展开 |
| 编译 | gcc -S myproc.i -o myproc.s |
.i → .s |
检查语法,翻译成汇编 |
| 汇编 | gcc -c myproc.s -o myproc.o |
.s → .o |
汇编代码 → 二进制目标文件 |
| 链接 | gcc myproc.o -o myproc |
.o → 可执行 |
和标准 C 库(libc.so.6)链接 |
3.第三版Makefile
3.1定义变量
先vim进文件里
然后在前面添加:
BIN=proc.exe
CC=gcc
SRC=myproc.c
FLAGS=-o
RM=rm -f
对应关系:
| 写法 | 意思 |
|---|---|
BIN=proc.exe |
以后用 $(BIN),就替换成 proc.exe |
CC=gcc |
以后用 $(CC),就替换成 gcc |
SRC=myproc.c |
以后用 $(SRC),就替换成 myproc.c |
FLAGS=-o |
以后用 $(FLAGS),就替换成 -o |
RM=rm -f |
以后用 $(RM),就替换成 rm -f |
cpp
proc.exe : myproc.c
↑ ↑
$@ $^
目标 依赖文件
3.2新的Makefile总代码
makefile
BIN=proc.exe # 目标文件名
CC=gcc # 编译器
SRC=myproc.c # 源文件
FLAGS=-o # 编译选项
RM=rm -f # 删除命令
$(BIN):$(SRC) # 换成变量引用:proc.exe:myproc.c
@$(CC) $(FLAGS) $@ $^ # @gcc -o proc.exe myproc.c
@echo "linking ... $^ to $@" # @echo "linking ... myproc.c to proc.exe"
.PHONY:
clean:
@$(RM) $(BIN) # @rm -f proc.exe
@echo "remove ... $(BIN)" # @echo "remove ... proc.exe"
.PHONY:test
test:
@echo $(BIN)
@echo $(CC)
@echo $(SRC)
@echo $(FLAGS)
@echo $(RM)
代入举例:
-
图中的代码:
(BIN):(SRC)
@(CC) (FLAGS) @ ^ -
相当于:
proc.exe:myproc.c
@gcc -o proc.exe myproc.c
4.最终版Makefile
目标只有一个:不管目录里有几个 .c,makefile 一行都不用改。
两个动作:
- ① 让 makefile 自动列出当前目录的源文件;
- ② 用模式规则
%.o:%.c一句话搞定"所有.c→ 同名.o"。
makefile
BIN=proc.exe
CC=gcc
# 自动列出当前目录所有 .c(写法二选一,一种注释掉)
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o) # 把 SRC 里的每个 .c 换成 .o
LFLAGS=-o # 链接选项
FLAGS=-c # 编译选项
RM=rm -f
$(BIN):$(OBJ)
@$(CC) $(LFLAGS) $@ $^
@echo "linking ... $^ to $@"
%.o:%.c # 模式规则:所有 .c 展开成同名 .o
@$(CC) $(FLAGS) $<
@echo "compling ... $< to $@"
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
.PHONY:test
test:
@echo $(SRC)
@echo $(OBJ)
关键点
| 语法 | 含义(老师板书原批注) |
|---|---|
$(wildcard *.c) |
makefile 内置函数 ,默认把当前目录所有 .c 通配出来。板书批注:"显示当前目录下所有的 .c 文件名" |
$(shell ls *.c) |
另一种写法:makefile 可以执行 shell 命令 。扫描到这行就执行 ls *.c,把结果存进 SRC。两种方式自己选 |
OBJ=$(SRC:.c=.o) |
替换 语法:把 SRC 内部每个"文件名 .c"换成"文件名 .o"。板书批注:"SRC 内部的文件名 .c → 文件名 .o" |
%.o:%.c |
模式规则 。% 是通配符,板书批注:"把当前路径下所有的 .o/.c 依次展开 "。目录里有 100 个 .c 就自动展开成 100 组依赖关系 |
$< |
表示当前正在处理的那一个 .c 文件(一条一条交给 gcc)。课件里把 $< 误写成 %<,以老师课上讲的 $< 为准 |
@ |
不回显命令,实现静默编译和静默清理 |
| 特殊符号速查 | 含义 |
|---|---|
$@ |
目标文件 |
$^ |
所有依赖文件 |
$< |
第一个依赖文件(模式规则里就是"当前那个 .c") |
@ |
命令前加它 → 不回显这条命令 |
