了解makefile基本知识

本文详细介绍了Makefile的工作原理和编写技巧,包括基本规则、变量、模式匹配、函数以及清理操作。通过实例展示了如何利用Makefile自动化编译过程,减少重复代码,提高效率。同时,文章提供了不同版本的Makefile实现,逐步优化,最终形成一个适用于多文件项目的高效Makefile模板。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

makefile文件是用来管理项目工程文件的,通过执行make命令,make就会解析并执行makefile文件。
使用 gcc 的命令进行程序编译时,对于单个文件或者同一路径下的少数文件编译是比较方便的,但是对项目中有很多文件,甚至这些文件不在同一个文件夹下,则必须使用 make 。
若当前目录下有写好的 makefile,则直接在当前目录下运行 make 即可。
文件名:makefile或Makefile

makefile 基本规则

# 每条规则的语法格式:
target1,target2...: depend1, depend2, ...
	command
	......
	......
# 命令可以是多个,每个命令前必须有一个Tab缩进并且独占占一行

每条规则由三个部分组成分别是目标文件(target),依赖文件(depend) 和命令(command)。

  • 命令: 通过执行什么命令由依赖文件生成目标文件
  • 依赖: 目标文件由哪些文件生成
  • 目标:要生成的目标文件

下面是一些简单的例子:

# 举例: 有源文件a.c  b.c  c.c  d.c  head.h, 需要生成可执行文件test
################# 例1 #################
test:a.c b.c c.c
	gcc a.c b.c c.c -o test

################# 例2 #################
# 有多个目标, 多个依赖, 多个命令
test, test1: a.c b.c c.c d.c
	gcc a.c b.c -o test
	gcc c.c d.c -o test1
	
################# 例3 #################	
# 规则之间的嵌套
# 优点是,若有的依赖文件没更新,则不会去重新编译它
test:a.o b.o c.o
	gcc a.o b.o c.o -o test
a.o:a.c
	gcc -c a.c -o a.o
b.o:b.c
	gcc -c b.c -o b.o
c.o:c.c
	gcc -c c.c -o c.o

自动推导

main: main.o func1.o func2.o
	gcc main.o func1.o func2.o -o main

makefile自动推导
可以发现上边的 makefile 文件中只有一条规则,依赖中所有的 .o 文件在本地项目目录中是不存在的,并且也没有其他的规则用来生成这些依赖文件,这时候 make 会使用内部默认的构造规则先将这些依赖文件生成出来,然后在执行规则中的命令,最后生成目标文件。

# make命令默认找makefile文件
# 也可以换个别名,但make要加-f选项
mv makefile mymake
make -f mymake

makefile 工作原理

make 会首先找到 makefile 文件中的规则,分析并执行相应的指令。
如果有的依赖文件不存在,则向下搜索规则,看是否有生成该依赖文件的规则:如果有规则,则执行生成依赖文件;如果没有则报错。
如果所有的依赖文件都存在,检查规则中的目标是否需要更新,必须先检查它的所有依赖文件,依赖文件中有任何一个被更新,则目标必须更新。

  • 目标时间戳 > 所有依赖的时间戳:规则中的命令不会被执行
  • 目标时间戳 < 某些依赖的时间戳:规则中的命令会被执行

如果不依赖任何条件(伪目标),则命令都会被执行

makefile 变量

上述例3的程序重复的很多,若文件很多,则必须要写出很多重复的语句,显得很冗余,所以,要采用 makefile 中的变量优化。

普通变量

自定义变量

# 创建一个变量名并且给其赋值
变量名=变量值
# 查看变量的值
$(变量名)

# 定义变量并赋值
obj=main.o func1.o func2.o
# 取变量的值
$(obj)
# 例
obj=main.o func1.o func2.o
tar=main
$(tar):$(obj)
	gcc $(obj) -o $(tar)

预定义变量

变量名含义
CCC 编译器的名称cc
CXXC++ 编译器的名称g++
CPPFLAGSC预处理选项-I
RM删除文件程序的名称rm -f
CFLAGSC 编译器的编译选项-Wall -g -c
LDFLAGS链接器选项-L -l
# 这是一个规则,普通写法
main: main.o func1.o func2.o
	gcc main.o func1.o func2.o -o main

# 使用了自定义变量和预定义变量
obj=main.o func1.o func2.o
target=main
CFLAGS=-O3  #代码优化
$(target):$(obj)
	$(CC) $(obj) -o $(target) $(CFLAGS)

自动变量

自动变量用来代表这些规则中的目标文件和依赖文件,并且它们只能在规则的命令中使用

变量含义
$@表示规则中的目标
$<表示规则中的第一个条件
$^表示规则中的所有条件,组成一个列表,以空格隔开,并且会自动去重
$*表示目标的名称,不包含目标的扩展名
# 普通写法
main: main.o func1.o func2.o
	gcc main.o func1.o func2.o -o main

# 使用自动变量
main: main.o func1.o func2.o
	gcc $^ -o $@

makefile 模式匹配

还是以例3为例,从第二个规则开始到第四个规则做的是相同的事情,但是由于文件名不同不得不在文件中写出多个规则,这就让 makefile 文件看起来非常的冗余,我们可以将这一系列的相同操作整理成一个模板

# 表示任意的 .c 和 .o 文件
%.c, %.o

# 表示所有的 .c 和 .o 文件
*.c, *.o
a.o:a.c
	gcc -c a.c -o a.o
b.o:b.c
	gcc -c b.c -o b.o
c.o:c.c
	gcc -c c.c -o c.o

# 简写后
%.c: %.o
	gcc $< -c $@

makefile 函数

makefile中有很多函数并且所有的函数都是有返回值的
makefile 中函数的格式: $(函数名 参数1, 参数2, 参数3, ...),主要目的是让我们能够快速方便的得到函数的返回值。

wildcard函数

获取指定目录下指定类型的文件名
其返回值是以空格分割的所有符合条件的文件名列表

$(wildcard PATTERN...)
# 参数:	指定某个目录, 搜索这个路径下指定类型的文件,比如:*.c
# 返回值:得到的若干个文件的文件列表,文件名之间使用空格间隔
# 例如
src=$(wildcard /home/duan/*.c *.c)
# 返回值格式: 
src=/home/duan/aa.c /home/duan/bb.c a.c b.c c.c

patsubst函数

按照指定的模式替换指定的文件名的后缀

# 有三个参数, 参数之间使用逗号间隔
$(patsubst <pattern>,<replacement>, <text>)
# 参数功能:
pattern: 指出要被替换的文件名中的后缀是什么, 比如:%.c
replacement: 指定参数pattern中的后缀最终要被替换为什么, 比如:%.o
text: 该参数中存储这要被替换的原始数据
# 返回值:
函数返回被替换过后的字符串
# 例如将所有的.cpp替换成.o
src = a.cpp b.cpp c.cpp e.cpp
# 把变量 src 中的所有文件名的后缀从 .cpp 替换为 .o
obj = $(patsubst %.cpp, %.o, $(src))

obj=a.o b.o c.o e.o

makefile 清理操作

# 通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标
# 在makefile中声明一个伪目标需要使用.PHONY关键字
.PHONY:伪目标名称
# 伪目标:执行下面命令时没有生成目标文件

# 如果直接这样写,clean就是makefile的基本规则,就会检查依赖文件,此时依赖文件是空,相当于都是最新的,所以输入make clean的时候不会执行
# 另外,如果当前目录下有clean文件,则make clean也不会执行对应的命令
clean:
	rm -rf *.o
	@rm -rf *.o	#不显示命令本身
# 综合
target=main
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o, $(src))
CC=gcc
CPPFLAGS=-I./
$(target): $(obj)
	$(CC) -o $@ $^
%.o: %.c
	$(CC) -c $< $(CPPFLAGS)

.PHONY:clean
clean:
	rm $(obj) $(target)

makefile 练习

版本1

calc:add.c div.c main.c mult.c sub.c
	gcc add.o div.o main.o mult.o sub.o -o calc
# 优点:书写简单
# 缺点:只要依赖中的某一个源文件被修改,所有的源文件都需要被重新编译,太耗时、效率低

版本2

# 默认所有的依赖都不存在, 需要使用其他规则生成这些依赖
calc:add.o div.o main.o mult.o sub.o
	gcc add.o div.o main.o mult.o sub.o -o calc

add.o:add.c
    gcc -c add.c -o add.o

div.o:div.c
    gcc -c div.c -o div.o

main.o:main.c
    gcc -c main.c -o main.o

sub.o:sub.c
    gcc -c sub.c -o sub.o

mult.o:mult.c
    gcc -c mult.c -o mult.o

# 优点:相较于版本 1 效率提升了
# 缺点:规则比较冗余,需要精简

版本3

# 添加自定义变量
obj=add.o div.o main.o mult.o sub.o
tar=calc
$(tar):$(obj)
        gcc $(obj) -o $(tar)

%.o:%.c
        gcc -c $< -o $@
# 优点:文件精简不少,变得简洁了
# 缺点:变量 obj 的值需要手动的写出来,如果需要编译的项目文件很多,都用手写出来不现实

版本4

# 使用函数搜索当前目录下的源文件 .c
src=$(wildcard *.c)
# 将源文件的后缀替换为 .o
obj=$(patsubst %.c, %.o, $(src))
tar=calc
$(tar):$(obj)
    gcc $(obj) -o $(tar)

%.o:%.c
    gcc -c $< -o $@
# 优点:解决了自动加载项目文件的问题,解放了双手
# 缺点:没有文件删除的功能,不能删除项目编译过程中生成的目标文件(*.o)和可执行程序

版本5

src=$(wildcard *.c)
obj=$(patsubst %.c, %.o, $(src))
tar=calc
$(tar):$(obj)
    gcc $(obj) -o $(tar)

%.o:%.c
    gcc -c $< -o $@

# 这个规则比较特殊, clean根本不会生成, 这是一个伪目标
clean:
    rm -rf $(obj) $(tar)

# 优点:添加了新的规则用于文件的删除,直接 make clean 就可以执行规则中的删除命令了
# 缺点:clean 会和文件夹下的文件名重名; 另外,由于没有依赖文件,所以 clean 永远是最新的文件,所以不会运行

版本6(最终版)

src=$(wildcard *.c)
obj=$(patsubst %.c, %.o, $(src))
tar=calc
$(tar):$(obj)
    gcc $(obj) -o $(tar)

%.o:%.c
    gcc -c $< -o $@

# 声明clean为伪文件
.PHONY:clean
clean:
    # - 表示强制这个指令执行, 如果执行失败也不会终止;
    # 如果不加,若命令执行失败,echo 语句是不会执行的
    -rm -rf $(obj) $(target) 
    echo "hello, 我是测试字符串"

makefile 进阶题

# 目录结构
.
├── include
│   └── head.h	==> 头文件, 声明了加减乘除四个函数
├── main.c		==> 测试程序, 调用了head.h中的函数
└── src
    ├── add.c	==> 加法运算
    ├── div.c	==> 除法运算
    ├── mult.c  ==> 乘法运算
    └── sub.c   ==> 减法运算
# 根据上边的项目目录结构编写的 makefile 文件如下:

tar = app
src=$(wildcard *.c ./src/*.c)
obj=$(patsubst %.c, %.o, $(src))
include=./include

$(tar):$(obj)
    gcc $^ -o $@

%.o:%.c
    gcc -c $< -o $@ -I$(include)

.PHONY:clean
clean:
    rm -rf $(obj) $(tar)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值