想要成为专业程序员,mekefile必须懂 !尤其是在Linux下进行软件编译,makefile就不得不自己写。因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个 Shell脚本一样,其中也可以执行操作系统的命令。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
这篇文章需要C/C++编译、Linux基础、GUN工具使用的相关知识,可以借鉴博主往期文章有详细讲解!
往期文章传送门:
史上最全的Linux常用命令汇总(超全面!超详细!收藏这一片就够了)
GUN工具的使用(gcc实现代码从编译到运行的详细介绍)
Shell基础学习笔记——文章最后送有Shell学习的电子书
makefile是make读入的唯一配置文件
*** missing separator. Stop.
makefile格式:
target : dependency_files ##dependency_files是依赖的文件
command #注意是一个TAB
例子:
hello.o:hello.c hello.h
gcc -c hello.c -o hello.o
新建f1.c、f2.c、main.c、head.h四个文件,并输入一下内容:
#include
void print1(){
printf("Message:f1.c\n");
}
#include
void print2()
{
printf("Message:f2.c\n");
}
void print1();
void print2();
#include
#include "head.h"#调用自己写的头文件用引号
int main()
{
print1();
print2();
printf("end main\n");
return 0;
}
在没有编写makefile之前可以使用命令gcc *.c -Wall
查看当前代码是否有语法错误。检查没有语法错误以后可以编写makefile文件。
test:f1.o f2.o main.o
gcc f1.o f2.o main.o -o test
f2.o:f2.c
gcc -c -Wall f2.c -o f2.o# -Wall允许发出gcc所有有用的报警信息
f1.o:f1.c
gcc -c -Wall f1.c -o f1.o#-c表示只编译不链接,生成目标文件“.o”
main.o:main.c
gcc -c -Wall main.c -o main.o#-o file表示把输出文件输入到file里
clean:
rm *.o test#删除.o和执行文件
执行makefile文件:
$ make #默认生成第一个文件
$ make '目标名'#选择性的编译
当工程中的文件名和makefile中的目标重名时,就会有伪目标。执行make命令时会发现提示目标文件已经是最新的了,将不被不执行!如果我想让makefile中某个命令永远被执行。可以在makefile目标前加上.PHONY:'目标名'
在makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在makefile中,变量可以使用在“目标”,“依赖目标”, “命令”或是Makefile的其它部分中。
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是区分英文字母大小写的。
创建和使用变量:
预定义变量:
变量名 | 变量含义 |
---|---|
- AR | 库文件维护程序名称,默认为ar.AS汇编程序名称,默认值为as。 |
- CC | C编译器的名称,默认为cc。CPP C预编译器的名称,默认值为$(CC) -E |
- CXX | C++编译器的名称,默认为g++ |
- FC | FORTRAN编译器的缩写,默认值为f77 |
- RM | 文件删除程序名称,默认为rm -f |
自动变量:
变量名 | 变量含义 |
---|---|
- $* | 不包含扩展名的目标文件名称 |
- $+ | 所有的依赖文件,以空格分开,并以出现后的先后为序,可能包含重复依赖文件 |
- $< | 第一个依赖文件的名称 |
- $? | 所有时间戳比目标文件晚的依赖文件,并以空格分开目标文件的完整名称 |
- $@ | 目标文件的完整名称 |
- $^ | 所有不重复的目标依赖文件,以空格分开 |
- $% | 如果目标是归档成员,则该变量表示目标的归档成员的目标名称 |
- 递归展开方式 VAR=var
- 简单方式 VAR: =var
一般使用递归展开方式进行变量定义
示例1:
dir :=/foo/bar
FOO?=bar
以上代码的含义是:如果FOO没有定义过,那么变量FOO的值就是‘bar’,如果FOO之前被定义过,那么什么也不做。
示例2:
#为变量添加值:可以通过+=为己定义的变量添加新的值
Main=hello.o hello-1.o
Main+=hello-2.o
参数 | 参数的作用 |
---|---|
-C | dir读入指定目录下的makefile |
-f | file读入当前目录下的file文件作为makefile |
-i | 忽略所有命令执行错误 |
-I | dir制定被包含的makefile所在目录 |
-n | 只打印要执行的命令,但是不执行这些命令 |
-p | 显示make变量数据库的隐含规则 |
-s | 在执行命令时不显示命令 |
-w | 如果执行make在执行过程中改变目录,打印当前目录名 |
当然makefile也可以像C语言一样调用其他的makefile的文件。
config.mk文件内容如下:
OBJS=f1.o f2.o
OBJS+=main.o
CFLAGS=-c -Wall -I include
makefile文件内容如下:
include config.mk#调用config.mk文件的内容
test:$(OBJS)
gcc $(OBJS) -o test
f2.o:$<
gcc $(CFLAGS) f2.c -o $@
f1.o:f1.c
gcc $(CFLAGS) f1.c -o $@
main.o:main.c
gcc $(CFLAGS) main.c -o mian.o
.PHONY:clean
clean:
rm *.o test
.o
的目标的依赖目标会自动推导为.c
,并且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS)
include config.mk
test:$(OBJS)
gcc $(OBJS) -o test
.PHONY:clean
clean:
rm *.o test
$(CC) $(LDFLAGS) n.o
x : x.o y.o#并且x.c、y.c都存在时,隐含命令如下
cc -c x.c -o x.o
cc -c y.c -o y.o
cc x.o y.o -o x
注意:如果没有一个源文件(如上例中的x.c)和目标名字(如上例中的x)相关联,那么最好写出自己的生成规则,不然,隐含规则会报错。
那么根据此隐含规则,我们又可以将makefile文件进行优化为以下内容:
include config.mk
f1:f1.o f2.o main.o
.PHONY:clean
clean:
rm *.o test
第二行代码中就是应用了规则二,目标文件可以改成f2、main都可以,但是必须是和所依赖的文件相关联,如果不关联(如:test)那么makefile就会报错。
VPATH:虚路径
VPATH
就是完成这么一个功能,如果没有指明这个变量,make只会在当前目录中去寻找依赖文件和目标文件。如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到指定的目录中去找寻文件了。VPATH = src:../headers
示例:
新建src1/f1.c、src2/f2.c、main/main.c、include/head.h、include/myinclude.h四个文件,并输入一下内容:
#include
void print1(){
printf("Message:f1.c\n");
}
#include
void print2()
{
printf("Message:f2.c\n");
}
void print1();
void print2();
#include
#include
#include "head.h"#调用自己写的头文件用引号
int main()
{
print1();
print2();
printf("end main\n");
return 0;
}
没有使用VPATH的的makefile内容如下:
CFLAGS=-c -Wall -I include
test:src1/f1.o src2/f2.o main/main.o
gcc src1/f1.o src2/f2.o main/main.o -o test
src1/f1.o:src1/f1.c#可以省略
gcc $(CFLAGS) $^ -o $@
src2/f2.o:src2/f2.c
gcc $(CFLAGS) $^ -o $@
main/main.o:main/main.c
gcc $(CFLAGS) $^ -o $@#可以省略
.PHONY:clean
clean:
find ./ -name "*.o" -exec rm {} \;;test#删除当前目录下所有的.o文件
以上代码中根据隐含规则可以简化——第4行~第9行可以省略。
利用VPATH进行优化makefile:
CFLAGS=-c -Wall -I include
VPATH=src1 src2 main
f1:src1/f1.o src2/f2.o main/main.o
.PHONY:clean
clean:
find ./ -name "*.o" -exec rm {} \;;test
当工程文件比较多时,如果只使用一个makefile会使得这个makefile显得比较繁琐复杂,那么我们可以通过使用makefile嵌套,内层使用子makefile,外层来调用这些子makefile。
如何来使用呢?举例说明如下:
subsystem:
cd subdir && $(MAKE)
这个例子可以这样来理解,在当前目录下有一个目录文件 subdir 和一个 makefile 文件,子目录 subdir 文件下还有一个 makefile 文件,这个文件是用来描述这个子目录文件的编译规则。使用时只需要在最外层的目录中执行 make 命令,当命令执行到上述的规则时,程序会进入到子目录中执行 make。这就是嵌套执行 make,我们把最外层的 Makefile 称为是总控 makefile。
上述的规则也可以换成另外一种写法:
subsystem
$(MAKE) -C subdir
在 make 的嵌套执行中,我们需要了解一个变量 “CURDIR”,此变量代表 make 的工作目录。当使用 make 的选项 “-C” 的时候,命令就会进入指定的目录中,然后此变量就会被重新赋值。总之,如果在 Makefile 中没有对此变量进行显式的赋值操作,那么它就表示 make 的工作目录。我们也可以在 Makefile 中为这个变量赋一个新的值,当然重新赋值后这个变量将不再代表 make 的工作目录。
export的使用:
使用 make 嵌套执行的时候,变量是否传递也是我们需要注意的。如果需要变量的传递,那么可以这样来使用:
export <variable>
如果不需要那么可以这样来写:
unexport <variable>
是变量的名字,不需要使用 “$” 这个字符。如果所有的变量都需要传递,那么只需要使用 “export” 就可以,不需要添加变量的名字。
Makefile 中还有两个变量不管是不是使用关键字 “export” 声明,它们总会传递到下层的 Makefile 中。这两个变量分别是 SHELL 和 MAKEFLAGS,特别是 MAKEFLAGS 变量,包含了 make 的参数信息。如果执行总控 Makefile 时,make 命令带有参数或者在上层的 Makefile 中定义了这个变量,那么 MAKEFLAGS 变量的值将会是 make 命令传递的参数,并且会传递到下层的 Makefile 中,这是一个系统级别的环境变量。
make 命令中有几个参数选项并不传递,它们是:"-C"、"-f"、"-o"、"-h" 和 “-W”。如果我们不想传递 MAKEFLAGS 变量的值,在 Makefile 中可以这样来写:
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,
用知识来改变命运
,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请“点赞” “评论” “收藏”
一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。