关于makefile
- 作者: 雪山肥鱼
- 时间:20210921 11:33
- 目的: 了解makefile
传统编译
三个文件,main.c print.c print.h



即使源文件很少,频繁的使用命令行编译也很烦
gcc main.c print.c -o a.out
不用指定头文件的原因是 print.h 文件在当前目录。
更别提有上百上千个 源文件 和头文件。
makefile 简单规则
文件名:可以Makefile, 也可以makefile, 但不能是MakeFile.
all(目标):所需要的文件
(只能是tab) gcc print.c main.c -o mytest
echo "gcc success"
改进一:
这是最简单的makefile,其实并不实用。
原因1: 即使源文件没有改动,依然会执行编译,文件少无所谓,文件多则会消耗时间。
编译原理:只会执行第一个目标。
all:print.c main.c
gcc print.c main.c -o myTest
- 判断 all 是不是一个文件,如果是文件,则会与后面的依赖文件进行对比。
- print.c main.c 也有时间,all 与 这两个文件的时间进行对比
- all 比 后面 所有依赖的时间都要新,则没必要重新编译
- 如果 后面依赖的文件比all 还要新,则会重新编译。
现在目标文件不存在,则文件时间为0, 永远老于后面的依赖文件,所以每次都会重新生成。
myTest: print.c main.c
gcc print.c main.c -o myTest
这样即可。
改进二
只要修改一个文件,就会编译整个工程,这样也不是一个合格的makefile。
阶段分开,生成.o 文件是一个阶段,链接是一个阶段。
myTest:print.o main.o
gcc print.o main.o -o myTest
print.o:print.c
gcc -c print.c
main.o:main.c
gcc -c main.c
这样只修改一个main.c ,就避免了修改一个文件,需要重新编译整个项目的问题。
- 还是会优先找第一个 目标文件,发现会依赖2个.o 文件。去找.o 的目标文件,对比main.c 与main.o 的时间。
- print.c 与 print.o 时间相同,说明没有更新
- main.o 的时间更新了,所以 myTest 与 main.o 对比,发现自己老了,则需要重新编译。
改进三
变量的替换
objs=printf.o main.o
target=myTest
$target:$(objs)
gcc $(objs) -o $(target)
print.o:print.c
gcc -c print.c
main.o:main.c
gcc -c main.c
改进四
重新编译的话,我们需要手动去删除.o 文件,这也不行的。
objs=printf.o main.o
target=myTest
$target:$(objs)
gcc $(objs) -o $(target)
print.o:print.c
gcc -c print.c
main.o:main.c
gcc -c main.c
clean:
rm -r $(objs) $(target)
echo "clean success"
直接 make clean 即可
但还是有问题,如果 文件不存在 就会报错,无法删除xxx.o 文件,没有哪个文件或者目录
如果失败,则后续的echo 命令都不执行
@隐藏掉echo 命令本身
clean:
-rm -r $(objs) $(target)
@echo "clean success"
make clean 失效
如果此时touch clean, 有一个clean 的目标文件
clean 这个目标: 无依赖文件, 则可以认为 clean 依赖的文件时间为0,永远最旧, 所以相对于 已经存在的 clean 文件,这个clean文件永远是最新的。所以没必要执行 下面 rm rf 的动作了。
没有 clean 文件, 则相当于 目标clean 也为0,则会重新生成。
所以进行修改,增加 phony 假的 虚假的目标
.PHONY:
clean:
-rm -rf $(obj) $(target)
@echo "clean success"
改进四
有很多的 .c 文件,该怎么办,难道依赖的.o 文件都要写一遍?
objs=printf.o main.o
target=myTest
$target:$(objs)
gcc $(objs) -o $(target)
%.o:%.c
gcc -c $^ -o $@
.PHONY
clean:
rm -r $(objs) $(target)
echo "clean success"
%o:.%c 对应的规则,一个.c 生成一个.o 的规则。
<: .o 对应的第一个.c 文件,也就是第一个所依赖项。
当前因为只有1个依赖c文件 所以两者一样,如果有两个,则不同。一个取全部,一个取一个
$@: 变量替换
符合这个条件的目标与依赖项,执行gcc 命令
备注
如果不写 构建规则:%.o : %.c
也会编译成功但是 是cc 进行编译。
什么是cc呢? 是一种内建规则。
make -p|less
/%.c
有一条内置规则
%.o:%.c
# recipe to execute(内置)
$(COMPILE.c) $(OUTOUT_OPTION) $<
其实 cc 就是 gcc
make -p > makefile.log// 查看各种变量的意义。须在makefile 所在的文件目录。

CFLAGS: 编译时刻的参数
CPPFLAGS: 预处理时刻的参数 -D -I -L
TARGET_ARCH:平台
LDFLAGS:链接阶段 (非内置,需要自己定义) -L 库路径 -l 哪个库
objs=printf.o main.o
target=myTest
CFLAGS=-g
CPPFLAGS=-DDEBUG
LDFLAGS=
$target:$(objs)
gcc $(objs) -o $(target) $(LDFLAGS)
%.o:%.c
gcc -c $^ -o $@
.PHONY
clean:
rm -r $(objs) $(target)
echo "clean success"
-DDEBUG的作用:
#ifdef DEBUG
#define LOG() puts("debug version");
#else
#define LOG()
#endif
没必要在代码里强写一个 #define DEBUG
可以在makefile 里面 对 CPPFLAGS 预编译选项进行处理
CPPFLAGS=-DDEBUG
改进五
(wildcard *.c) 可以查找当前目录下的所有.c 文件 然后把所有.c文件的列表 修改成.o
(wildcard *.c)) 将所有.c 文件 替换成.o。
objs=$(pats ubst %c, %.o, $(wildcard *.c))
target=myTest
CFLAGS=-g
CPPFLAGS=-DDEBUG
LDFLAGS=
$target:$(objs)
gcc $(objs) -o $(target) $(LDFLAGS)
%.o:%.c
gcc -c $^ -o $@
.PHONY
clean:
rm -r $(objs) $(target)
echo "clean success"
安装 与 卸载
.PHONY:clean install distclean
install:
cp $(target) /usr/local/bin -f //库 拷贝到 /usr/lib/ 中
distclean:
rm -rf /usr/local/bin/$(target)
改进六
目录嵌套, 子目录中也有make, 将所有的makefile 都执行一遍
.PHONY:clean install distclean all
all:
make $(target)
male -C subdir
make all 相当于执行 第一条 (objs)
make -C subdir 进入子目录,再执行一次make。
最终:
objs=$(pats ubst %c, %.o, $(wildcard *.c))
target=myTest
CFLAGS=-g
CPPFLAGS=-DDEBUG
LDFLAGS=
$(target):$(objs)
gcc $(objs) -o $(target) $(LDFLAGS)
%.o:%.c
gcc -c $^ -o $@
.PHONY:clean install distclean all
all:
make $(target)
make -C subdir
install:
cp $(target) /usr/local/bin -f //库 拷贝到 /usr/lib/ 中
distclean:
rm -rf /usr/local/bin/$(target)
clean:
rm -r $(objs) $(target)
echo "clean success"