前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >makefile 基础、进阶及常用 makefile

makefile 基础、进阶及常用 makefile

作者头像
我与梦想有个约会
发布2023-10-21 14:12:57
2990
发布2023-10-21 14:12:57
举报
文章被收录于专栏:jiajia_dengjiajia_deng

makefile 语法

代码语言:javascript
复制
目标:依赖
(tab)命令

如:add.o:add.c
(一个tab缩进)gcc –Wall –g –c add.c –o add.o

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

makefile 工作原理

1、若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来生成该依赖文件 2、检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新

  • 分析各个目标和依赖之间的关系
  • 根据依赖关系自底向上执行命令
  • 根据修改时间比目标新,确定更新
  • 如果目标不依赖任何条件,则执行对应命令,以示更新

一个最简单的 makefile

代码语言:javascript
复制
main:main.c
    gcc main.c -o main

该 makefile 生成目标为 main 的文件,依赖 main.c,所需命令是 gcc main.c -o main,注意前面的 (tab)。

联合编译 makefile

上面的例子只是一个最简单的 makefile 的使用方法,但实际项目里面不可能只有一个文件,实际可能是多个 .c .h 组成,像这样的项目,我们该如何通过 makefile 管理呢?我们来看下面这个项目的目录。

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/makefile$ tree
.
├── add.c
├── main.c
├── makefile
├── mul.c
└── sub.c

目录中有 main.c 是项目的 mian 函数入口代码,里面用到了三个函数,分别是 add()、sub()、mul(),他们都再不同的 .c 文件中,如果我们手动编译这个项目需要用到如下命令:

代码语言:javascript
复制
gcc main.c add.c sub.c mul.c -o app

执行上面命令后会执行联合编译,直接生成可执行文件 app。而有些时候你会发现,这样编译带来的问题是如果我只改动了其中一个文件的代码,执行这个编译语法的时候,会重新编译所有的代码,小工程也就算了,如果是个大项目那肯定会很慢,所以这不是上上之选。正常的做法应该是先使用 -c 参数生成每个文件的 .o 文件,然后联合编译所有的 .o 文件,当某个 .c 文件修改后,只重新编译这个 .c.o,然后再执行联合编译,这样就减少了多余代码编译的过程,方法如下:

代码语言:javascript
复制
gcc -c main.c add.c sub.c mul.c

这样就生成了 4 个 .o 文件。

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/makefile$ tree
.
├── add.c
├── add.o
├── main.c
├── main.o
├── makefile
├── mul.c
├── mul.o
├── sub.c
└── sub.o

然后对 4 个 .o 文件执行联合编译,最终生成可执行的 app 文件。

代码语言:javascript
复制
gcc main.o add.o sub.o mul.o -o app

以上是我们通过手动敲命令的方式执行编译,这种联合编译,我们如何通过 makefile 来处理呢?先来分析一下,我们把手动执行编译的过程逆向思考一下,想生成目标为可执行的 app 文件,需要依赖 4 个 .o 文件的支持,main.o add.o sub.o mul.o。,所需执行的命令gcc main.o add.o sub.o mul.o -o app,那么下面的 makefile 语法应运而生了。

代码语言:javascript
复制
app:main.o add.o sub.o mul.o
    gcc main.o add.o sub.o mul.o -o app

但这里你会发现,如果我们项目目录是空的,只有 .c.h 文件时,根本没有 .o 文件,makefile 去哪找这些 .o 文件呢?所以我们还需要制作一些其他的目标文件,那就是这些 .o 文件。语法如下:

代码语言:javascript
复制
main.o:main.c
    gcc -c main.c

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

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

mul.o:mul.c
    gcc -c mul.c

组合后的 makefile 如下:

代码语言:javascript
复制
app:main.o add.o sub.o mul.o
    gcc main.o add.o sub.o mul.o -o app

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

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

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

mul.o:mul.c
    gcc -c mul.c

以上就是一个算是能实现功能的 makefile 了,但是还不算完美,后面我们再引入其他 makefile 的特性,先在这个项目目录下执行一次 make 命令,看看编译的效果如何。

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/makefile$ make
gcc -c main.c
gcc -c add.c
gcc -c sub.c
gcc -c mul.c
gcc main.o add.o sub.o mul.o -o app

执行 make 命令后我们可以看到,首先对每个 .c 文件进行编译,生成 .o 文件,然后对几个 .o 文件联合编译生成可执行的 app 文件。并且如果你修改了其中一个 .c 文件的情况下,重新执行 make 命令,make 只会重新编译你修改过的源文件,并不会重新编译所有。

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/makefile$ vi main.c
mycode@vmware:~/Desktop/code/makefile$ make
gcc -c main.c
gcc main.o add.o sub.o mul.o -o app

makefile 变量

接下来我们引入 makefile 变量机制,来修改一下上面的 makefile 文件。

代码语言:javascript
复制
obj = main.o add.o sub.o mul.o

app:$(obj)
    gcc $(obj) -o app

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

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

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

mul.o:mul.c
    gcc -c mul.c

clean:
    rm -rf $(obj) app

执行后,同样可以达到效果,我们引入了一个 obj 的变量,注意变量在使用的时候要加 $(),中间变量名字。并且增加了一个 clean 目标,他不依赖任何东西,执行一条清除所有 .o 文件和 app 文件的命令。用来帮助我们清理项目目录。使用方法就是在 make 命令后加 clean 参数即可,以下是执行后的效果:

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/makefile$ make clean
rm -rf main.o add.o sub.o mul.o app
mycode@vmware:~/Desktop/code/makefile$ ls
add.c  main.c  makefile  mul.c  sub.c

makefile 自动变量

makefile 中有一些预定义的变量,你可以理解它像是 C 语言中的一些关键字,分别有不同的意义,我们列举几个常用的自动变量(其他还有很多),通过上面的 makefile 文件做修改来演示他们的用法。

代码语言:javascript
复制
$@:在命令中使用,表示规则中的目标
$<:在命令中使用,表示规则中的第一个条件
$^:在命令中使用,表示规则中的所有条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复项。

他们的使用方法如下:

代码语言:javascript
复制
obj = main.o add.o sub.o mul.o

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

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

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

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

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

clean:
    rm -rf $(obj) app

我们分别把这三个自动变量放到了不同的位置,用来表示他们的作用。可以一目了然。执行 make 命令后,可以达到同样的效果。

makefile 模式规则

再分析一下上面的 makefile 代码,对于每个要生成的 .o 文件,我们都要给他写一条规则,如果有很多怎么办?难道要一条一条的写吗?当然不会,makefile 提供了一种模式规则,使用 % 符号来匹配任意字符串达到通配的作用,先来看改造后的代码,然后我们来分析其执行流程。

代码语言:javascript
复制
obj = main.o add.o sub.o mul.o

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

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

clean:
    rm -rf $(obj) app

上面的 makefile 经过改造后,只剩下这么一点内容了,但别看内容少,一样可以达到我们的需求。其执行流程是要生成的最终目标为 appapp 需要 4 个 .o 文件的支持,这 4 个文件我们用了一个变量 obj 来表示。当去找 main.o 文件时,发现没有,则去下面找是否有匹配生成这个 .o 文件的规则,因为 %.o 可以匹配 main.o,所以找到了下面这条规则:

代码语言:javascript
复制
%.o:%.c
    gcc -c $< -o $@

规则中的 %.c 的 % 值,取决于目标 %.o,而此时 %.o% 的值是上面生成 app 所需的 main.o,所以解释以后的代码相当于下面这样:

代码语言:javascript
复制
main.o:main.c
    gcc -c $< -o $@

根据这条规则,生成出了 main.o 文件,其他 3 个 .o 文件也依次类推。这样就实现自动化的去匹配生成规则。

mekfile 函数

如果你认为上面的 makefile 已经很完美了,那你就大错特错了,做一个假设,如果你在项目中新增了一个 .c 的文件后,你还是需要修改 makefile 增加一个所依赖的 .o 文件。想自动化实现这个步骤,如果你自己写脚本,你是不是应该考虑,有多少个 .c 文件就生成多少个 .o 文件,而且每个 .o 文件的名字都与 .c 一样,所以我们可以获取一份 .c 文件的列表,根据这份列表把所有后缀改为 .o 再形成一个新的列表,这份列表就作为生成最终 app 的依赖。想实现这样的功能我们就需要用到 makefile 中的函数!如下所示:

代码语言:javascript
复制
# 获取所有 .c 文件的列表赋值给 src 变量
src = $(wildcard *.c)
# 根据 src 变量获取 .o 文件列表存放到 obj 变量中
obj = $(patsubst %.c, %.o, $(src))

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

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

clean:
    rm -rf $(obj) app

这次无论你增加多少个 .c 文件,makefile 都会自动遍历到并生成 .o 文件了。我们也可以添加一些自定义的变量,让以后 makefile 维护起来更方便:

代码语言:javascript
复制
# 获取所有 .c 文件的列表赋值给 src 变量
src = $(wildcard *.c)
# 根据 src 变量获取 .o 文件列表存放到 obj 变量中
obj = $(patsubst %.c, %.o, $(src))

# 方便后面更换编译器
CC = gcc
# 一些通用的编译参数
CFLAGS = -Wall -g

app:$(obj)
    $(CC) $^ -o $@ 

%.o:%.c
    $(CC) -c $< -o $@ $(CFLAGS)

clean:
    rm -rf $(obj) app

我们在生成 .o 文件时增加了 -Wall-g 参数,这样我们就可以看到编译时是不是有警告信息了,因为我这个项目没有引入头文件告诉编译器去哪里找实现函数,所以就会出现一些警告信息,当然我们目的并不是要处理这些警告,而是来描述 makefile 的使用,看这篇文章的人,你可以自己根据需求修改。

makefile 中的 all

因为 makefile 的执行流程是找到第一个目标作为最终生成的目标,如果顺序错乱了,makefile 就可能报错,all 方法就是解决这个问题而存在的,并且,all 方法可以让一个 makefile 生成多个目标。示例如下:

代码语言:javascript
复制
src = $(wildcard *.c)
obj = $(patsubst %.c, %.o, $(src))

CC = gcc
CFLAGS = -Wall -g

all:app main

%.o:%.c
    $(CC) -c $< -o $@ $(CFLAGS)

app:$(obj)
    $(CC) $^ -o $@ 

main:$(obj)
    $(CC) $^ -o $@ 

clean:
    rm -rf $(obj) app

makefile clean 方法优化

make clean 命令是用来清除目录下临时文件的,执行 clean 这个目标时,不需要任何依赖项,也就意味着,如果目录下有一个文件名为 clean 的话,执行 make clean 命令时会判断这个目标所依赖的内容是否有变化,如有变化则重新生成,无变化则跳过,而恰恰我们这个 clean 没写依赖规则!这将导致 clean 无论如何都不会被执行。解决这个问题的办法就是将 clean 方法声明为一个_伪目标_,做就就是让 clean 无论如何都更新,同样我们生成的 all 目标也可能会出现这种情况,所以我们将它们两个都声明为伪目标,方法如下:

代码语言:javascript
复制
src = $(wildcard *.c)
obj = $(patsubst %.c, %.o, $(src))

CC = gcc
CFLAGS = -Wall -g

all:app main

%.o:%.c
    $(CC) -c $< -o $@ $(CFLAGS)

app:$(obj)
    $(CC) $^ -o $@ 

main:$(obj)
    $(CC) $^ -o $@ 

clean:
    -rm -rf $(obj) main app 

.PHONY:clean all

这样即使目录下有名为 cleanall 的文件时,也不影响 make clean 的执行。同时注意看代码的人可能也发现了,rm -rf $(obj) main app 命令前增加了一个 - 符号,这个符号的目的就是如果这条命令执行失败了继续执行,不影响后续命令的执行。 至此 makefile 的功能说明到此为止一,下面就是收集的一些常用做测试用的 makefile 代码。

常用 makefile

将目录下所有 .c 文件都分别生成为可执行文件,比如有 sort.c、readfile.c、writefile.c,他们都分别有自己的 main 函数,我们希望生成时生成 3 个可执行文件,分别叫 sort、readfile、writefile,那使用以下 makefile 即可实现。

代码语言:javascript
复制
src = $(wildcard *.c)
dsc = $(patsubst %.c, %, $(src))

CC = gcc
CFLAGS = -Wall -g

all:$(dsc)

%:%.c
    $(CC) $^ -o $@ 

clean:
    -rm -rf $(dsc) 

.PHONY:clean all

一个有目录规则的 makefile 代码,我们的目录如下所示:

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/project$ tree
.
├── bin
├── inc
│   └── head.h
├── makefile
├── obj
└── src
    ├── add.c
    ├── mian.c
    ├── mul.c
    └── sub.c

4 directories, 6 files

bin 是生成最终目标文件的目录 inc 是存放头文件的目录 obj 是生成的 .o 文件目录 src 是源文件目录 makefile 如下:

代码语言:javascript
复制
src = $(wildcard ./src/*.c)
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))
inc = ./inc/

CC = gcc
CFLAGS = -Wall -g
CPPFLAGS = -I

all:./bin/app

./bin/app:$(obj)
    $(CC) $^ -o $@ $(CFLAGS)

./obj/%.o:./src/%.c
    $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(inc)

clean:
    -rm -rf $(obj) ./bin/app

.PHONY:clean all

执行后的结果

代码语言:javascript
复制
mycode@vmware:~/Desktop/code/project$ make
gcc -c src/add.c -o obj/add.o -Wall -g -I ./inc/
gcc -c src/mian.c -o obj/mian.o -Wall -g -I ./inc/
gcc -c src/mul.c -o obj/mul.o -Wall -g -I ./inc/
gcc -c src/sub.c -o obj/sub.o -Wall -g -I ./inc/
gcc obj/add.o obj/mian.o obj/mul.o obj/sub.o -o bin/app -Wall -g
mycode@vmware:~/Desktop/code/project$ tree
.
├── bin
│   └── app
├── inc
│   └── head.h
├── makefile
├── obj
│   ├── add.o
│   ├── mian.o
│   ├── mul.o
│   └── sub.o
└── src
    ├── add.c
    ├── mian.c
    ├── mul.c
    └── sub.c

4 directories, 11 files
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • makefile 语法
  • makefile 工作原理
  • 一个最简单的 makefile
  • 联合编译 makefile
  • makefile 变量
  • makefile 自动变量
  • makefile 模式规则
  • mekfile 函数
  • makefile 中的 all
  • makefile clean 方法优化
  • 常用 makefile
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档