目录
makefile
在vs中,vs默认帮我们做了自动化构建
在linux中,我们要使用make/makefile自动化项目的构建
其中
make是一个命令makefile是一个文件
简单展示Makefile
shell
$ ls
test.c
$ touch Makefile
$ vim Makefile
$ cat Makefile
test.exe:test.c
gcc -o test.exe test.c
$ make
gcc -o test.exe test.c
$ ls
Makefile test.c test.exe
make命令与makefile:依赖关系与依赖方法
test.exe:test.c表明依赖关系
gcc -o test.exe test.c表明依赖方法
makefile会形成推导栈
makefile
test.exe:test.o
gcc test.o -o test.exe
test.o:test.s
gcc -c test.s -o test.o
test.s:test.i
gcc -S test.i -o test.s
test.i:test.c
gcc -E test.c -o test.i
shell
$ make
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o test.exe
$ ll
-rw-rw-r-- 1 157 Dec 14 21:31 Makefile
-rw-rw-r-- 1 74 Dec 14 19:47 test.c
-rwxrwxr-x 1 8440 Dec 14 21:31 test.exe
-rw-rw-r-- 1 16872 Dec 14 21:31 test.i
-rw-rw-r-- 1 1496 Dec 14 21:31 test.o
-rw-rw-r-- 1 447 Dec 14 21:31 test.s
会根据依赖关系形成推导栈 ,然后依次出栈
本质就是一种脚本!
清理功能 :
在里面生成伪目标
shell
$ cat Makefile
test.exe:test.c
gcc -o test.exe test.c
.PHONY:clean
clean:
rm -f test.exe test.i test.s test.o
执行clean命令
shell
$ make clean
rm -f test.exe test.i test.s test.o
$ ls
Makefile test.c
- 细节1:依赖关系必须存在,依赖文件列表可以为空
- 细节2:依赖方法可以是任何shell命令
- 细节3 :clean目标,只是利用make的自动推导的能力,让他执行了
rm命令
在构建工程的视角,看起来就是在清理项目
清理项目,本质就是删除不需要的临时文件 - 细节4 :
make命令,后面可以跟"目标名"
后面跟谁,就解析谁的依赖关系和依赖方法
make默认只会推导一条完整的推导链路
make默认只会推导第一个依赖关系(最佳实践要把clean放后面)
其中,.PHONY 表示声明一个符号(名字自定义)
用于修饰目标文件是一个伪目标
本质:总是被执行的!
什么叫总是被执行的?
shell
$ ls
Makefile Makefile-backup test.c
$ make
gcc -o test.exe test.c
$ make
make: `test.exe' is up to date.
在Makefile中,test.exe:test.这句并没有被声明为总是被执行的
因此在完成一次之后,就不允许再执行
如果改为
makefile
.PHONY:test.exe
test.exe:test.c
gcc -o test.exe test
此时再执行:
shell
$ ls
Makefile Makefile-backup test.c
$ make
gcc -o test.exe test.c
$ make
gcc -o test.exe test.c
就可以总是被执行了
这也是为什么make clean可以一直被执行!
.PHONY是如何实现总是被执行的?
因为.c和.exe的本质都是文件,那么它们的属性都会有时间
shell
$ stat test.c
File: 'test.c'
Size: 74 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1186657 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1004/ suliiik) Gid: ( 1005/ suliiik)
Access: 2025-12-14 19:51:24.398090793 +0800
Modify: 2025-12-14 19:47:18.493390146 +0800
Change: 2025-12-14 19:47:18.493390146 +0800
Birth: -
$ stat test.exe
File: 'test.exe'
Size: 8440 Blocks: 24 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 1186660 Links: 1
Access: (0775/-rwxrwxr-x) Uid: ( 1004/ suliiik) Gid: ( 1005/ suliiik)
Access: 2025-12-14 22:03:49.973706809 +0800
Modify: 2025-12-14 22:03:49.973706809 +0800
Change: 2025-12-14 22:03:49.973706809 +0800
Birth: -
如何判断源文件是否需要被重新编译?
源文件和可执行谁更新?
是通过比较两个文件的修改时间来比较的!!
而.PHONY就是忽略了文件Mod时间这个条件,因此可以总是被执行!
如果希望更新一个文件的修改时间,可以
shell
$ touch test.c
这样可以在不修改文件内容的前提下更新文件修改时间
关闭打印
现在每次执行make命令,都会打印出其中的命令(回显)
shell
$ make
gcc -o test.exe test.c
$ make clean
rm -f test.exe test.i test.s test.o
这样既不优雅也不美观
在Makefile里面给命令加上@之后就可以不打印了
makefile
test.exe:test.c
@gcc -o test.exe test.c
.PHONY:clean
clean:
@rm -f test.exe test.i test.s test.o
此时不会打印任何信息:
shell
$ vim Makefile
$ make clean
$ make
想要改变.c/.exe的文件名,一定要改Makefile吗?
假设我把test.c改名为code.c,则每次都要打开Makefile进行修改文件名字,非常麻烦
因此可以在开头定义变量
makefile
BIN=test.exe
SRC=test.c
$(BIN):$(SRC)
@gcc -o test.exe test.c
.PHONY:clean
clean:
@rm -f test.exe test.i test.s test.o
在编译时,BIN和SRC会类似于宏替换一样代入$()的位置
但是上面的命令无法正常执行,因为@gcc -o test.exe test.c等命令是硬编码
因此,要改为变量名
makefile
BIN=test.exe
SRC=test.c
$(BIN):$(SRC)
@gcc -o $@ $^
.PHONY:clean
clean:
@rm -f $(BIN)
其中,在gcc/g++中,@是一个特殊的内置变量
如果项目里面有大量.c文件该如何写makefile
方法一:使用shell命令
使用ls命令,将*.c文件全都写入SRC
makefile
BIN=mycode.exe
SRC=$(shell ls *.c)
.PHONY:test
test:
@echo "Debug------"
@echo $(SRC)
@echo "Debug------"
但是最佳实践并非如此
方法二:使用wildcard函数
wildcard函数:获取当前目录下所有的源文件
makefile
BIN=mycode.exe
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c)
.PHONY:test
test:
@echo "Debug------"
@echo $(SRC)
@echo "Debug------"
想要生成.o文件怎么办
makefile
BIN=mycode.exe
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
$(BIN):$(OBJ)
gcc -o $@ $^
%.o:%.c
gcc -c $<
.PHONY:test
test:
@echo "Debug------"
@echo $(SRC)
@echo "Debug------"
@echo $(OBJ)
@echo "Debug------"
%是一个通配符 ,%.o相当于把所有.o文件展开
$<的意思是:将:右边的文件一个个对应左边的文件
会展开为
makefile
main.o:main.c
gcc -c main.c
src1.o:src1.c
gcc -c src1.c
......
clean命令
makefile
BIN=mycode.exe
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
$(BIN):$(OBJ)
@gcc -o $@ $^
%.o:%.c
@gcc -c $<
.PHONY:clean
clean:
@rm -f $(OBJ) $(BIN)
最佳实践
makefile
BIN=mycode.exe
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
Echo=echo
Rm=rm -rf
$(BIN):$(OBJ)
@$(CC) -o $@ $^
@$(Echo) "linking $^ to $@ ... done"
%.o:%.c
@$(CC) -c $<
@$(Echo) "compling $< to $@ ... done"
.PHONY:clean
clean:
$(Rm) $(OBJ) $(BIN)
文件时间:
Access:文件被访问的更新时间(读取内容或执行)
Modify:文件内容的更新时间
Change:文件属性的更新时间
-
为什么修改内容后
Change时间也更新了?因为文件大小、inode 等元数据随之变化,触发了
Change时间更新。 -
为什么使用
cat不一定会更新Access?对于一个文件的操作,"查看"这一行为的占比极大
如果每次查看操作都要更新时间,那么意味着每次查看都要修改文件属性
那就意味着每次查看操作都需要刷新磁盘
会增加访问磁盘的次数,增加IO,导致外设效率低下,OS整体效率降低
现代OS优化后,会在 访问特定次数/间隔一段时间 后进行更新一次时间
补充背景知识
回车换行
回车:指回到当前行的最左侧
换行:移动到下一行