makefile终极教程

来源:微信公众号「编程学习基地」

什么是makefile?

或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。因为,makefile关系到了整个工程的编译规则。

上期编译链接,你还不会用GCC生成标准库?已经详细介绍了GCC编译链接的过程

makefile介绍

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

makefile的规则是:
1.如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

​ 2.如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

​ 3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

makefile的规则

在讲述这个makefile之前,还是让我们先来粗略地看一看makefile的规则。

target ... : prerequisites ...
    command
    ...
    ...
  • target

    可以是一个object file(目标文件),也可以是一个执行文件。

  • prerequisites

    生成该target所依赖的文件和/或target

  • command

    该target要执行的命令(任意的shell命令)

一个示例

首先还是使用上期 编译链接,你还不会用GCC生成标准库?的测试代码

div.c add.c div.c mult.c sub.c head.h

├── Calculator
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

那么我们需要通过 makefile 将示例代码编译生成目标文件 app

第一个版本
app:sub.o mult.o div.o add.o main.o
	gcc sub.o mult.o div.o add.o main.o -o app
sub.o:sub.c
	gcc -c sub.c
mult.o:mult.c
	gcc -c mult.c
div.o:div.c
	gcc -c div.c
add.o:add.c
	gcc -c add.c
main.o:main.c
	gcc -c main.c
#伪指令
.PHONY:clean
clean:
	rm -f sub.o mult.o div.o add.o main.o app

执行 make 命令看一下效果,很 nice

[root@Calculator]# make
gcc -c sub.c
gcc -c mult.c
gcc -c div.c
gcc -c add.c
gcc -c main.c
gcc sub.o mult.o div.o add.o main.o -o app

思考:为什么写这么复杂,先生成.o再生成.c

直接告诉你答案:方便编译链接

小实验:修改 add.c 里面的内容,随便按一个空格,然后保存退出再执行make命令

[root@Calculator]# make
gcc -c add.c
gcc sub.o mult.o div.o add.o main.o -o app

结果很明显:只对add.c进行了编译,省略了没有必要的编译步骤

为什么会这样?那就要说说 make 是如何工作的

make是如何工作的

在默认的方式下,也就是我们只输入 make 命令。那么,

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“app”这个文件,并把这个文件作为最终的目标文件。
  3. 如果app文件不存在,或是app所依赖的后面的 .o 文件的文件修改时间要比 app 这个文件新,那么,他就会执行后面所定义的命令来生成 app 这个文件,节省了没有必要的编译步骤。
  4. 如果 app 所依赖的 .o 文件也不存在,那么make会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生成make的终极任务,也就是执行文件 app 了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。

上述还只是简单的makefile,属于显式规则,那么为了优化makefile我们介绍隐式规则

makefile中使用变量

在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点类似C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上,可以直接把变量当成C语言中的宏理解。

Makefile中变量有四种定义(赋值)方式:

1,简单赋值( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效(推荐使用)

2,递归赋值( = )赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响

3,条件赋值( ?= )如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。

4,追加赋值( += )原变量用空格隔开的方式追加一个新值

使用变量非常简单,变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $ 符号,但最好用小括号 () 或是大括号 {} 把变量给包括起来。

OBJ:=main.o	#定义变量
	#引用变量
${OBJ} 		
	#使用变量
$(OBJ)		#推荐使用

除了自己定义的变量之外makefile还提供了预定义的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值

命令的变量
变量默认命令意义
AR默认命令是 ar函数库打包程序。
CC默认命令是 ccC语言编译程序。
CXX默认命令是 g++C++语言编译程序。
CPP默认命令是 $(CC) –EC程序的预处理器(输出是标准输出设备)。
RM默认命令是 rm –f删除文件命令。
命令参数的变量
命令意义
CFLAGSC语言编译器参数。
CXXFLAGSC++语言编译器参数。
CPPFLAGSC预处理器参数
LDFLAGS链接器参数。(如: ld

隐晦规则

如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。

通配符
符号含义
%任意一个
匹配一个字符
*所有

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令

例如:

只要make看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中,如果make找到一个 main.o ,那么 main.c就会是main.o` 的依赖文件。

OBJ:=sub.o mult.o div.o add.o main.o
$(TARGET):$(OBJ)
	${CC} $(OBJ) -o $(TARGET)

有.o文件没有.c文件,makefile会自动推导生成.o文件

除了通配符,makefile还提供了自动推导的自动变量

自动变量
符号含义
$@代表目标文件
$^代表所有依赖文件
$<代表第一个依赖文件

由此第二个版本出来了

第二个版本
CC:=gcc
TARGET:=app     #目标变量
OBJ:=sub.o mult.o div.o add.o main.o
$(TARGET):$(OBJ)
	${CC} $(OBJ) -o $(TARGET)
.PHONY:clean
clean:
	$(RM) $(OBJ) $(TARGET)

已经很精简了是不是

伪指令

在第一第二个版本的makefile里面我都有写.PHONY:clean这个规则,并且在make的时候并没有执行这个规则。 其实.PHONY 表示 clean 是一个“伪目标”,并不在make的执行命令中,只有指定才会执行例如:make clean

比较健壮的伪指令写法是:

.PHONY:clean
clean:
	-rm -f $(OBJ) $(TARGET)

rm 命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事.

函数

在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。下面介绍三个最常用的函数

文本处理函数

wildcard

$(wildcard PATTERN...)

功能:该函数被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。

举例

获取工作目录下的所有.c文件列表

SRC:=$(wildcard *.c)
字符串替换函数

patsubst

$(patsubst <pattern>,<replacement>,<text>)

功能:查找 <text> 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 <pattern> ,如果匹配的话,则以 <replacement> 替换。

举例

SRC:=$(wildcard *.c)
OBJ:=$(patsubst %.c,%.o,$(SRC))	#将SRC里面的.c文件替换成.o文件
shell函数

shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。

$(shell <option>)

举例

SRC = $(shell find . -name "*.c")

将当前目录及其子目录下所有文件后缀为 .c 的文件以空格为限赋值给 SRC

最终版本

先总结一下前面都讲了些什么

Makefile里主要包含了五个东西:显式规则隐晦规则变量定义函数注释

  1. 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者明显指出要生成的文件、文件的依赖文件和生成的命令。
  2. 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写 Makefile,这是由make所支持的。
  3. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
  4. 函数。其包主要介绍了三个函数,一个是提取工作目录下的所有.c文件列表,另外一个就是将提取的.c列表转换成.o列表,最后就是shell函数,可以执行任何shell操作.
  5. 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用 # 字符,这个就像C/C++中的 // 一样。如果你要在你的Makefile中使用 # 字符,可以用反斜杠进行转义,如: \#

然后最终版本确定,可以作为模板使用

TARGET=app
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c,%.o,$(SRC))
DEFS =-DDEBUG
CFLAGS =-g 
CC =gcc
LIBS =  
$(TARGET):$(OBJ)
	$(CC) $(CFLAGS) $(DEFS) -o $@ $^ $(LIBS)
.PHONY:
clean:
	rm -rf *.o $(TARGET)

多目录makefile

作为一个健壮的makefile怎么能将所有代码放在一个文件夹下面呢?优秀的工程师都是分模块标准放置

先看一下目录树形结构

├── add
│   ├── add.c
│   └── Makefile
├── div
│   ├── div.c
│   └── Makefile
├── include
│   └── head.h
├── main.c
├── Makefile
├── Makefile.build
├── mult
│   ├── Makefile
│   └── mult.c
└── sub
    ├── Makefile
    └── sub.c

示例程序的Makefile分为3类:

  1. 顶层目录的Makefile
  2. 顶层目录的Makefile.build
  3. 各级子目录的Makefile

各级子目录的Makefile

这个是最简单的,只需要obj-y+=将所有.o文件或者子级目录添加即可,例如

sub文件夹下的 makefile

obj-y += sub.o

顶层目录的Makefile

它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链、编译参数

CFLAGS = -g							#编译器参数
CFLAGS += -I $(shell pwd)/include	#指定 include 包含文件的搜索目录
LDFLAGS := -lm						#链接器参数

export CFLAGS LDFLAGS	

TOPDIR := $(shell pwd)
export TOPDIR

TARGET := app


obj-y += main.o
obj-y += sub/
obj-y += mult/
obj-y += div/
obj-y += add/

all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o


clean:
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)

distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

顶层目录的Makefile.build

这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为.o文件.

这个也不好演示,但都是模板,改一些参数就可以直接用,微信公众号【编程学习基地】后台发送关键字makefile获取测试源码和makefile文件

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeRoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值