专栏首页编程学习基地makefile终极奥义

makefile终极奥义

什么是makefile?

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

上期「用GCC写个库给你玩」已经详细介绍了GCC编译链接的过程,那么接下来就聊聊makefile艺术。

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 文件的文件修改时间要比 edit 这个文件新,那么,他就会执行后面所定义的命令来生成 edit 这个文件,节省了没有必要的编译步骤。
  4. 如果 edit 所依赖的 .o 文件也不存在,那么make会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生成make的终极任务,也就是执行文件 edit 了。

这就是整个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

默认命令是 cc

C语言编译程序。

CXX

默认命令是 g++

C++语言编译程序。

CPP

默认命令是 $(CC) –E

C程序的预处理器(输出是标准输出设备)。

RM

默认命令是 rm –f

删除文件命令。

命令参数的变量

命令

意义

CFLAGS

C语言编译器参数。

CXXFLAGS

C++语言编译器参数。

CPPFLAGS

C预处理器参数

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文件.

End

本文分享自微信公众号 - 编程学习基地(LearnBase),作者:deroy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-01-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • cmake终极奥义

    上期makefile终极奥义反响不错,有粉丝提出有没有cmake终极奥义,那么她来了。已构建项目,地址为:

    DeROy
  • PowerBI 时间智能终极奥义,用 WTD 练手

    单纯讲解时间智能函数犹如盲人摸象,不见全貌,更不见本质。 我们之前写过很多关于时间智能函数的文章,但文本将是最为本质以及最重要的。本文属于 BI佐罗 Powe...

    BI佐罗
  • 搜推实战-终极奥秘!

    P.S. 本文作为 炼丹笔记 第100篇原创文章,为了答谢这一路走来大家对炼丹笔记的关注与支持,本文文末,我们安排一次抽奖活动,送上时晴小姐姐为大家准备的青轴机...

    炼丹笔记
  • Linux 开发 | 学习 Makefile

    Make 是控制工程中通过源码生成可执行文件和其他相关文件的工具。Make 通过 Makefile 获取如何编译、链接和安装清理工程的信息。

    orientlu
  • 【Linux笔记】make工程管理工具(二)

    上一篇笔记分享了使用make工具编译C程序的方法(【Linux笔记】make工程管理工具(一)),但是还未分享make工具是什么,本篇笔记就来看一下make工具...

    正念君
  • Makefile经典教程(掌握这些足够)

    makefile很重要       什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个...

    bear_fish
  • Make

    ###一、make的功能: make是一个用来维护程序模块关系和生产可执行文件的工具,他可以根据程序修改的情况重新编译链接生成的中间代码或最终的可执行文件。执行...

    用户1214695
  • 跟我一起写Makefile

    makefile 介绍 make命令执行时,需要一个 makefile 文件,以告诉make命令如何去编译和链接程序。 首先,我们用一个示例来说明makefil...

    _gongluck
  • 跟我一起写Makefile:MakeFile介绍

    http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:...

    bear_fish

扫码关注云+社区

领取腾讯云代金券