Makefile 编写指南
杨豪迈
Makefile的编写规则概述
target ... : prerequisites...
command...
每一条Makefile的编译规则由三部分组成,分别是target:目标,prerequisites先决条件和command具体生成的命令
- target是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)
- prerequisites是生成target的所有依赖文件。所以如果有一个或多个prerequisites中定义的文件比target文件要新的时候,就会重新执行command命令
- command就是make时需要执行的命令
Makefile是如何工作的
- 读入所有的Makefile。
- 读入被include的其它Makefile。
- 初始化文件中的变量。
- 推导隐晦规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
final_app : main.o utils.o
gcc -o final_app main.o utils.o
在默认的方式下,也就是我们只输入make命令。那么,
-
make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
-
如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“final_app”这个文件,并把这个文件作为最终的目标文件。
-
如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
-
如果final_app所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
-
当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件final_app了。
make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
Makefile编写综述
1. Makefile基本书写规则
规则举例
foo.o : foo.c def.h
gcc -g -o foo.o foo.c
规则语法
target : prerequisites ;command
command
-
targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。
-
command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。(见上)
-
prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。
-
如果命令太长,你可以使用反斜框(‘/’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。
-
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
2. 在Makefile中使用通配符
make支持三各通配符:* ? 和 [...]
。这是和Unix的B-Shell是相同的。
举例子:
target : *.o
command
clean:
rm -f *.o
3. 在Makefile中的文件搜索
有两种方法可以达到文件控制文件搜索范围的目的
- 通过改变VPATH环境变量
VPATH=src:../include
我们可以通过更改VPATH变量来指定依赖文件和目标文件的目录,其中VPATH当中的不同路径通过:
分隔开
- 通过使用make命令的vpath关键字
vpath <pattern> <directories>
我们通过vpath关键字为符合pattern模式匹配的文件名称指定搜索目录directories
举例
vpath %.h ../headers
#其中%表示有0或者若干个字符
vpath %.c foo
vpath % blish
vpath %.c foo:bar
#我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。
4. Makefile当中的伪目标
“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
- 伪目标通过
.PHONEY
关键字修饰
举例
#示例1
#clean并不会真正的生成相应的clean文件,所以clean是一个伪目标
clean:
rm *.o temp
.PHONEY: clean
#示例2
#通过运行make可以生成多个可执行文件,其中all代表那三个生成的可执行文件,但是all本身并不会生成,所以将all定义为伪目标
all : prog1 prog2 prog3
.PHONEY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
5. Makefile中的静态模式
targets : target-pattern : prereq-patterns
command
-
targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
-
target-parrtern是指明了targets的模式,也就是的目标集模式。
-
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。
下面通过举例说明静态模式的用法
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $(INCLUDE_FILE) $< -o $@
这其中指明了我们的目标从
(
o
b
j
e
c
t
)
当
中
选
取
。
‘
‘
‘
(object)当中选取。```
(object)当中选取。‘‘‘<和
$@```都是自动化变量,分别代表依赖集和目标集
所以上述式子等价于
foo.o : foo.c
$(CC) -c $(CFLAGS) $(INCLUDE_FILE) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) $(INCLUDE_FILE) bar.c -o bar.o
6. 在Makefile中自动生成依赖
在Makefile中引用其他Makefile
这里我们穿插一下引用其他Makefile的方法
include a.mk b.mk
通过内建的include语句就可以将其他Makefile里面的内容放在对应include的位置
如果你希望Makefile不理睬那些无法读取的Makefile,可以添加一个-
例如
-include a.mk b.mk
如何自动生成依赖
这一部分我个人暂时还没有完全理解透彻,等我理解了再给大家补充,详细资料可以暂时参考自动依赖生成的官方文档,如下
https://www.gnu.org/software/make/manual/make.html#Automatic-Prerequisites
7. Makefile中的命令规范
- 显示命令
可以通过使用 echo show something
来在显示信息
但是默认情况下Makefile会输出echo命令的内容,所以通常使用
show_cmd:
@echo show something
在命令开始添加@来防止echo内容的输出
- 执行命令
test1:
cd test
pwd
test2:
cd test;pwd
这里通过两个命令的执行说明Makefile执行命令的原理。
- Makefile在执行每一个目标命令时,将每一行开启子shell执行,所以如果两个命令不在同一行,前一个命令就不会对后一个命令造成影响
- 命令出错
每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。
但是命令出错并不一定意味着就是错误的,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。
#例如:
-include $(INCL_DIR)
clean:
-mkdir $(SOME_DIR)
-rm -f *.o
#这样子就可以忽略命令的错误
- 镶套执行Makefile
有时候,如果我们的工程量之分庞大的时候,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中
假如,我们的工程目录有一个子文件夹subdir,这个目录下有一个子Makefile,那么我可以这样子执行Makefile
subpro:
mkdir subdir && make
#或者
subpro:
make -C subdir
如果你想向下一级Makefile传递变量,可以通过export的方式将变量传递给下一级
如果你在export变量之后又不想让变量在下一个Makefile中传递到下一级,可以使用unexport方法
subpro:
my_var=something
export my_var
make -C subdir
unexport my_var
make -C another_subdir
但是在Makefile当中,有两个变量,无论你是否export,都会传递到下一级,那就是SHELL
和MAKEFLAGS
这时候你可以通过显试的改变他达到阻止传递到下一层
subpro:
mkdir subdir && make MAKEFLAGS=
- 定义命令包
我们可以通过定义命令包,达到化简命令的方式,下面举例说明命令包的作用
define run-gcc
gcc -c $^ -o $@
endef
foo.o : foo.c
$(run-gcc)
其中 $(run-gcc)
会被替换成为gcc -c foo.c -o foo.o
。$@
代表的是target,$^
代表的是依赖项
8. 在Makefile中使用变量
- 理解变量的含义
在Makefile当中,变量默认是通过宏的方式做替换使用的,变量取值时需要通过$
符号与小括号$()
括起来。
例如:
a=$(b)
b=1
test:
@echo $(a)
在需要调用$(a)
的地方,会通过宏替换的方式,将$(a)
替换成$(b)
,然后再将$(b)
替换成1,所以最终的输出就是1
但是这样会造成一些奇怪的现象,例如:
b=2
a=$(b)
b=1
c=$(b)
test:
@echo the value of a is ${a},the value of b is ${c}
这样子程序的输出结果并不是我们想象中的2和1,而是a和c会同时输出1,这就是宏替换的原因。
如果想要阻止这种情况的发生,可以通过:=
来代替=
的方式达到防止这种情况发生的目的。
如果是通过:=
进行赋值,make就会通过直接递归替换掉所有的宏,直到赋值成为最原始的值。这样子就可以防止上述情况的发生。例如:
b=2
a:=$(b)
b=1
c:=$(b)
test:
@echo the value of a is ${a},the value of b is ${c}
这样最终程序输出的结果就会2和1了
- 变量的高级用法
- 变量的结尾替换
foo:=a.o b.o c.o
bar=$(foo:%.o=%.c)
通过给变量foo提供模式匹配,直接替换变量中每一项的后缀名
- 镶套取值
x = $(y)
y = z
z = Hello
a := $($(x))
变量的取值符号可以进行镶套,最终a的值是"Hello"
- 追加变量值
#方法1:
foo=a.o b.o
foo:=$(foo) c.o
#这里必须使用:=,因为上面解释过,不然会出现宏镶套的情况,产生编译错误
#方法2:
foo=a.o b.o
foo+=c.o
- 通过变量生成变量
a=b
$(a)_value=c
我们同样可以通过变量的值生成变量,这样子就可以生成一个名为b_value的变量
- 局部变量
在Makefile当中,默认的变量都是全局的,但是针对一条特定的规则,我们也可以为他定制局部变量,这样在执行这条规则和这条规则推导下的所有规则时候,都可以使用相应的局部变量了
局部变量示例如图所示:
#定义Makefile的全局变量
var=a
#为test1规则声明一条局部变量
test1: var=b
#定义test1规则
test1: test2
@echo test1 $(var)
test2:
@echo test2 $(var)
test3:
@echo test2 $(var)
- 如果运行
make test1
,就会输出
test1 b
test2 b
- 如果运行
make test2
,输出
test2 a
- 运行
make test3
,输出
test3 a
- 模式变量
模式变量也是一种局部变量,在符合target模式的规则中使用
例如:
%.o : CFLAGS+=-o
通过这种模式变量的方式,在所有的target包含.o的规则当中,CFLAGS中都添加了-o选项
9. 在Makefile中使用条件判断
我们可以在Makefile当中使用条件判断使我们的Makefile更加灵活
使用示例:
a=a
foo:
ifeq ($(a),a)
echo a is a
else
echo a is not a
endif
这样子规则foo可以根据不同的$(a)来编译生成不同的规则
- 相等条件判断
语法:
#是否相同
ifeq ($(a),b)
do something
else
do something
endif
#是否不同
ifneq ($(a),b)
do something
else
do something
endif
示例:
#如果a值为空,那么a=b
ifeq ($(a),)
a=b
endif
- 是否有值条件判断
语法:
#如果有值
ifdef a
do something
else
do something
endif
#如果没有值
ifndef a
do something
else
do something
endif
注意,ifdef只会判断变量当前是否有值,并不会将变量的值扩展过来。未来方便理解,下面考虑几种情况
#情况一
b=
a=$(b)
#情况二
b=
a:=$(b)
ifdef a
a=defined
else
a=undefined
endif
test:
@echo $(a)
-
针对情况一,a相当于一个宏,虽然b是没有值的,但是a是有值的,所以输出defined
-
针对情况二,a被递归复制成b的值,也就是没有值,所以输出是undefined
10. 在Makefile中使用函数
在Makefile中可以使用函数,用法规则如下:
$(function arguement)
#其中arguement中变量通过逗号分割
下面,我简单介绍一些在Ccodebase里面出现过的函数
- strip
用法:
$(strp <text>)
strip 函数可以去掉字符串text中开头和结尾的空隔,然后返回开头结尾没有空格的字符串
- foreach
用法:
$(foreach <var>,<list>,<text>)
功能:
这个函数的意思是,把参数<list
>中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式。每一次<text>
会返回一个字符串,循环过程中,<text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。
<var>
最好是一个变量名,<list>
可以是一个表达式,而<text>
中一般会使用<var>
这个参数来依次枚举<list>
中的单词。
示例:
ames := a b c d
files := $(foreach n,$(names),$(n).o)
最终的返回值是a.o b.o c.o d.o
- filter-out
用法:
$(filter-out <pattern...>,<text>)
功能:
以<pattern>
模式过滤<text>
字符串中的单词,去除符合模式<pattern>
的单词。可以有多个模式。
返回:
返回不符合模式<pattern>
的字串。
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
返回值是foo.o bar.o
。
- wildcard
用法:
$(wildcard <pattern>)
功能:
wildcard函数用来获取当前make目录下所有的符合<pattern>
模式的所有文件的名称组成的字符串,通过空格分开
这里需要强调注意的是,wildcard函数支持的是Unix通配符,和%等Makefile通配符相区分
示例:
$(wildcard *.c)
返回当前目录下的所有.c结尾的文件名称列表
- patsubst
用法:
$(patsubst <pattern>,<replacement>,<text>)
功能:
查找<text>
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换。这里,<pattern>
可以包括通配符“%”,表示任意长度的字串。如果<replacement>
中也包含“%”,那么,<replacement>
中的这个“%”将是<pattern>
中的那个“%”所代表的字串。(可以用“/”来转义,以“/%”来表示真实含义的“%”字符)
返回:
函数返回被替换过后的字符串。
举例:
$(patsubst %.c,%.o,x.c.c bar.c)
返回值是x.c.o bar.o
- shell
用法:
$(shell <command>)
shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量。
示例:
files := $(shell echo *.c)
files就是当前目录下的所有.c文件了
11.运行Makefile
- 指定Makefile
我们可以通过-f选项选择当前目录下需要运行的Makefile文件
make -f my_make.mk
- 指定Makefile的目标
每一次输入make命令,make都会执行一个目标。
- 如果不输入参数,就是默认第一个规则作为执行的目标。
- 也可以指定执行的目标,例如
make clean
就会把clean规则作为本次执行的目标。
在执行make命令是,make命令的参数可能是以-
打头(例如make -f
等make命令的命令行参数),也可能是包含=
的环境变量(例如
make DEBUG=1
这一类初始化的环境变量)。剩下的就是make命令的目标了。
例如:
make -f my_make.mk DEBUG=1 prog1 prog2
这其中prog1和prog2就是make命令的目标
在Makefile运行时,通过一个环境变量MAKECMDGOALS
储存make命令的目标列表
即然make可以指定所有makefile中的目标,那么也包括“伪目标”。在Unix世界中,软件发布时,特别是GNU这种开源软件的发布时,其makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的makefile中的目标。
all
这个伪目标是所有目标的目标,其功能一般是编译所有的目标。clean
这个伪目标功能是删除所有被make创建的文件。install
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。print
这个伪目标的功能是例出改变过的源文件。tar
这个伪目标功能是把源程序打包备份。也就是一个tar文件。dist
这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。TAGS
这个伪目标功能是更新所有的目标,以备完整地重编译使用。check
和test
这两个伪目标一般用来测试makefile的流程。
12.自动化变量
我们前文中提到过很多的自动化变量,这些变量在规则中扮演着不同的含义
下面我们总汇一下常用的自动化变量的含义
变量 | 含义 |
---|---|
$@ | 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。 |
$< | 依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 |
$? | 所有比目标新的依赖目标的集合。以空格分隔。 |
$^ | 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。 |
13. 隐晦规则
隐晦规则是Makefile之所以强大的原因之一
“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。
“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。
我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。
我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟,亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。
例如,我们有下面的一个Makefile:
foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在上面的那个例子中,make调用的隐含规则是,把[.o]的目标的依赖文件置成[.c],并使用C的编译命令cc –c $(CFLAGS) [.c]
来生成[.o]的目标。也就是说,我们完全没有必要写下下面的两条规则:
foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)
下面我们来看看常用的隐藏规则
- 编译C程序的隐含规则
<n>.o
的目标的依赖目标会自动推导为<n>.c
,并且其生成命令是$(CC) –c $(CPPFLAGS) $(CFLAGS)
- 编译C++程序的隐含规则
<n>.o
的目标的依赖目标会自动推导为<n>.cc
或是<n>.C
,并且其生成命令是$(CXX) –c $(CPPFLAGS) $(CFLAGS)
- 链接Object文件的隐含规则
<n>
目标依赖于<n>.o
,通过运行C的编译器来运行链接程序生成(一般是ld
),其生成命令是:$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
重载隐晦规则
你可以这样重载.o文件生成的隐晦规则:
%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)
也可以这样取消重载
%.o : %.s
在后面不写命令就行
附录
gcc命令参数说明
gcc 与 g++ 分别是 gnu 的 c & c++ 编译器 gcc/g++ 在执行编译工作的时候,总共需要4步:
- 预处理,生成 .i 的文件
【预处理器cpp】
- 将预处理后的文件不转换成汇编语言, 生成文件 .s
【编译器egcs】
- 汇编变为目标代码(机器代码)生成 .o 的文件
【汇编器as】
- 连接目标代码, 生成可执行程序
【链接器ld】
常用参数详解
-x language filename
定文件所使用的语言, 使后缀名无效, 对以后的多个有效。
以使用的参数吗有下面的这些:'c', 'objective-c', 'c-header', 'c++', 'cpp-output', 'assembler', 与 'assembler-with-cpp'
举例:
gcc -x c hello.whatever
-c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
举例:
gcc -c hello.c
最终生成hello.o
-S
只激活预处理和编译,就是指把文件编译成为汇编代码。
举例:
gcc -S hello.c
最终生成hello.s
-E
只激活预处理,这个不生成文件, 你需要把它重定向到一个输出文件里面。
举例:
gcc -E hello.c > test.txt
gcc -E hello.c | more
最终生成预处理过后的文件
-o
制定目标名称, 默认的时候, gcc 编译出来的文件是 a.out, 很难听, 如果你和我有同感,改掉它, 哈哈。
举例:
gcc -o hello.exe hello.c
gcc -o hello.asm -S hello.c
-Dmacro
相当于在代码中#define macro
-Dmacro=defn
相当于在代码中#define macro defn
-Umacro
相当于在代码中#undef macro
-g
在编译的时候加入调试信息
-O0 -O1 -O2 -O3
编译器优化的四个等级,-O0不优化,-O3优化等级最高
-shared
生成动态链接库
举例:
gcc -shared -o s.so s.c
根据s.c生成动态链接库s.so
-ansi
关闭gcc中所有与ansi标准不兼容的特性,并且激活ansi的专有特性
-include file
相当于在代码中添加了#include"file"
举例:
gcc hello.c -include /somewhere/something.h
-imacros file
将 file 文件的宏, 扩展到 gcc/g++ 的输入文件, 宏定义本身并不出现在输入文件中。
-I dir
通过-I
参数指定头文件的首选寻找路径
-L dir
指定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然编译器将只在标准库的目录找。这个dir就是目录的名称。
-lname
这个选项是一个-l
加上一个name
,表示在库文件的目录下搜索名为libname.so
的动态链接库,如果找不到,就搜索名为libname.a
的静态链接库。如果有-static
,则直接搜索名为libname.a
的静态链接库
举例:
#假设此时在当前目录下面的test目录下有一个名为foo.a的静态链接库,我们现在需要在链接时链接该库
gcc -L test -ltest -o main main.c
-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。
-share
这个命令和上面的恰好相反
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库。
-pthread
-pthread
是gcc一个较新的特性,用来取代-lpthread
。
使用-pthread
选项会在程序链接时候链接pthread动态链接库,并且声明-D_REENTRANT
,这个宏为其他的一些c语言标准库提供了线程安全的特性
所以在编写基于pthread的多线程程序是多使用这个链接选项
-W -w -Wall
这是编译是的警告选项
-w
选项表示忽略所有的警告
-W
表示只显示编译器认为会出错的选项
-Wall
表示显示所有的警告
-Wname
在-W
后面加上警告的名称,表示开启相关警告的提示
-M -MM
-M自动生成文件的依赖信息,参见Makefile自动生成依赖的方式
-MM也是生成文件的依赖信息,但是不会生成标准库之类的冗杂的依赖,只生成必要的依赖(也就是忽略#include<file>
尖括号的依赖)
示例:
gcc -M hello.c > hello.d
gcc -MM hello.c > hello.d
-MD
通过-M
生成依赖并且自动保存到文件当中,文件名称可以通过-o
指定,或者跟.c
文件同名,后缀名为.d
-MMD
通过-MM
生成依赖并且自动保存到文件当中,文件名称可以通过-o
指定,或者跟.c
文件同名,后缀名为.d