前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【makefile】

【makefile】

原创
作者头像
菜到飞起
发布2023-04-05 23:05:07
1.9K0
发布2023-04-05 23:05:07
举报
文章被收录于专栏:个人学习只用

注:个人学习所记,仅供参考

实验七:Makefile实验

实验原理

在Linux或Unix环境下,对于只含有几个源代码文件的小程序(如hello.c)的编译,可以手工键入gcc命令对源代码文件逐个进行编译;然而在大型的项目开发中,可能涉及几十到几百个源文件,采用手工键入的方式进行编译,则非常不方便,而且一旦修改了源代码,尤其头文件发生了的修改,采用手工方式进行编译和维护的工作量相当大,而且容易出错。所以在Linux或Unix环境下,人们通常利用GNU make工具来自动完成应用程序的维护和编译工作。

实际上,GNU make工具通过一个称为Makefile的文件来完成对应用程序的自动维护和编译工作。Makefile是按照某种脚本语法编写的文本文件,而GNU make能够对Makefile中指令进行解释并执行编译操作。Makefile文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。GNU make工作时的执行步骤如下:

1、读入所有的Makefile。 2、读入被include的其它Makefile。 3、初始化文件中的变量。 4、推导隐晦规则,并分析所有规则。 5、为所有的目标文件创建依赖关系链。 6、根据依赖关系,决定哪些目标要重新生成。 7、执行生成命令。

1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。下面对makefile的相关问题进行简单介绍:

1、Makefile的基本结构

Makefile的一般结构:

target……:dependency……

command……

结构中各部分的含义:

(1)target(目标):一个目标文件,可以是Object文件,也可以是执行文件。还可以是一个标签(Label)。

(2)dependency(依赖):要生成目标文件(target)所依赖哪些文件

(3)command(命令):创建项目时需要运行的shell命令

注:命令(command)部分的每行的缩进必须要使用Tab而不能使用多个空格)。

Makefile实际上是一个文件的依赖关系,也就是说, target这一个或多个的目标文件依赖于dependency中的文件,其生成规则定义在命令command中。如果依赖文件(dependency)中有一个以上的文件比目标(target)文件要新的话,shell命令(command)所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

例如,假设有一个C源文件test.c,该源文件包含有自定义的头文件test.h,则目标文件test.o明确依赖于两个源文件:test.c和test.h。如果只希望利用gcc命令来生成test.o目标文件,这时,就可以利用如下的makefile来定义test.o的创建规则:

代码语言:javascript
复制
#This makefile just is a example. 
test.o: test.c test.h
    gcc –c test.c

注意,第一个字符为#的行表示注释行

第一个非注释行指定 test.o 为目标,并且依赖于 test.c 和 test.h 文件。随后的行指定了如何从目标所依赖的文件建立目标。

当 test.c 或 test.h 文件在编译之后又被修改,则make工具可自动重新编译 test.o ,如果在前后两次编译之间,test.c和test.h均没有被修改,而且test.o还存在的话,就没有必要重新编译。这种依赖关系在多源文件的程序编译中尤其重要,通过这种依赖关系的定义,make工具可避免许多不必要的编译工作。

一个makefile文件中可定义多个目标,利用make target命令可指定要编译的目标,如果不指定目标,则使用第一个目标。通常,makefile中定义有clean目标,可用来清除编译过程中的中间文件。

代码语言:javascript
复制
# This makefile just is a example. 
test.o: test.c test.h
    gcc -c  test.c
clean:
    rm -f *.o

运行make  clean时,执行rm –f  *.o命令,删除编译过程中生成的所有的以 .o 结尾的文件。

2、Makefile的基本内容

Makefile一般包括包含:显式规则、变量定义、隐含规则、文件指示和注释等五个内容。

(1)显式规则:显式规则说明如何生成一个或多个的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

(2)变量定义:在Makefile中可以定义一系列的变量,变量一般都是字符串,当Makefile被执行时,变量的值会被扩展到相应的引用位置上。

(3)隐含规则:由于GNU make具有自动推导功能,所以隐晦规则可以比较粗糙地简略地书写Makefile,然后由GNU make的自动推导功能完成隐晦规则的内容。

(4)文件指示:其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。

(5)注释:Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,如果你要在你的Makefile中使用“#”字符,可以用反斜杠进行转义,如:“\#”。

2.1 Makefile中的变量

(1)Makefile中定义的变量,与C/C++语言中的宏一样,代表一个文本字串,在Makefile被执行时候变量会自动地展开在所使用的地方。Makefile中的变量可以使用在“目标”,“依赖目标”,“命令”或Makefile的其它部分中。

(2)Makefile中变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 “ : ”、“ # ”、“ = ”、空字符(空格、回车等)。

(3)Makefile中变量是大小写敏感的。“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式。

(4)变量在声明时需要给予初值,而在使用时,需要在变量名前加上 “ $ ” 符号。

代码语言:javascript
复制
CC=gcc
CFLAGS=
OBJS=hello.o
all: hello
hello: $(OBJS)
	$(CC) $(CFLAGS) $(OBJS) -o hello
hello.o: hello.c
	$(CC) $(CFLAGS) -c hello.c -o $(OBJS)
clean:
	rm –rf hello *.o

上面自定义变量OBJS表示hello.o,当makefile被执行时,变量会在使用它的地方精确地展开,就像C/C++中的宏一样。上述makfile变量展开后的形式为:

代码语言:javascript
复制
CC=gcc
CFLAGS=
OBJS=hello.o
all: hello
hello: hello.o
	gcchello.o -o hello
hello.o: hello.c
	gcc -c hello.c -o  hello.o
clean:
	rm -rf hello *.o

GNU make的主要预定义变量

GNU make 有许多预定义的变量,这些变量具有特殊的含义,可在规则中使用。以下给出了一些主要的预定义变量,除这些变量外,GNU make 还将所有的环境变量作为自己的预定义变量。

$@ ——表示规则中的目标文件集。在模式规则中,如果有多个目标,那么," $@ " 就是匹配于目标中模式定义的集合。

$% ——仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

$< ——依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

$? ——所有比目标新的依赖目标的集合,以空格分隔。

$^ ——所有的依赖目标的集合,以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$+ ——这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

命令的变量。

AR   函数库打包程序。默认命令是 “ar”。

AS 汇编语言编译程序。默认命令是“as”。

CC  C语言编译程序。默认命令是“cc”。

CXX C++语言编译程序。默认命令是“g++”。

CO 从 RCS文件中扩展文件程序。默认命令是“co”。

CPP C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。

FC  Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。

GET 从SCCS文件中扩展文件的程序。默认命令是“get”。

LEX Lex方法分析器程序(针对于C或Ratfor)。默认命令是“lex”。

PC Pascal语言编译程序。默认命令是“pc”。

YACC Yacc文法分析器(针对于C程序)。默认命令是“yacc”。

YACCR Yacc文法分析器(针对于Ratfor程序)。默认命令是“yacc –r”。

MAKEINFO 转换Texinfo源文件(.texi)到Info文件程序。默认命令是“makeinfo”。

TEX 从TeX源文件创建TeX DVI文件的程序。默认命令是“tex”。

TEXI2DVI 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是“texi2dvi”。

WEAVE 转换Web到TeX的程序。默认命令是“weave”。

CWEAVE 转换C Web 到 TeX的程序。默认命令是“cweave”。

TANGLE 转换Web到Pascal语言的程序。默认命令是“tangle”。

CTANGLE 转换C Web 到 C。默认命令是“ctangle”。

RM  删除文件命令。默认命令是“rm –f”。

命令参数变量:

下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。 ARFLAGS 函数库打包程序AR命令的参数。默认值是“rv”。 ASFLAGS 汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。 CFLAGS C语言编译器参数。 CXXFLAGS C++语言编译器参数。 COFLAGS RCS命令参数。 CPPFLAGS C预处理器参数。( C 和 Fortran 编译器也会用到)。 FFLAGS Fortran语言编译器参数。 GFLAGS SCCS “get”程序参数。 LDFLAGS 链接器参数。(如:“ld”) LFLAGS Lex文法分析器参数。 PFLAGS Pascal语言编译器参数。

RFLAGS Ratfor 程序的Fortran 编译器参数。 YFLAGS Yacc文法分析器参数。

2.2隐含规则

GNU make 包含有一些内置的或隐含的规则,这些规则定义了如何从不同的依赖文件建立特定类型的目标。

GNU make 支持两种类型的隐含规则:

(1)后缀规则(Suffix Rule)。后缀规则是定义隐含规则的老风格方法。后缀规则定义了将一个具有某个后缀的文件 (例如,.c 文件)转换为具有另外一种后缀的文件(例如,.o 文件)的方法。每个后缀规则以两个成对出现的后缀名定义,例如,将 .c 文件转换为 .o 文件的后缀规则可定义为:

代码语言:javascript
复制
.c.o: 
    $(CC) $(CCFLAGS) $(CPPFLAGS) -c -o $@ $<

(2)模式规则(pattern rules)。这种规则更加通用,因为可以利用模式规则定义更加复杂的依赖性规则。模式规则看起来非常类似于正则规则,但在目标名称的前面多了一个 % 号,同时可用来定义目标和依赖文件之间的关系,例如下面的模式规则定义了如何将任意一个 X.c 文件转换为 X.o 文件:

代码语言:javascript
复制
%.c:%.o 
    $(CC) $(CCFLAGS) $(CPPFLAGS) -c -o $@ $<

2.3 文件引用

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。

例如:有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

代码语言:javascript
复制
include foo.make *.mk $(bar)

等价于:

代码语言:javascript
复制
include foo.make a.mk b.mk c.mk e.mk f.mk   

make命令开始时,会寻找 include 所指出的其它Makefile,并把其内容安置在当前的位置。如果文件都没有指定绝对路径或是相对路径的话,make首先会在当前目录下寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

(1)如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。 (2)如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。

2.4 Makefile中的函数

在Makefile中可以使用函数来处理变量,从而让命令或规则更为的灵活和具有智能,函数调用,很像变量的使用,也是以“$”来标识的,函数调用后,函数的返回值可以当做变量来使用。

例如:'wildcard' 的函数,可以展开成一列所有符合由其参数描述的文件名,文件之间以空格间隔。语法如下:

代码语言:javascript
复制
#用'wildcard' 函数找出当前目录中所有的".c"文件
SOURCES=$(wildcard *.c)

3、运行 make

3.1 Make的执行

一般来说,最简单的就是直接在命令行下输入make命令,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行,也可以给make命令指定一个特殊名字的Makefile,要求使用make的“-f”参数,例如:make -f Hello.makefile.

3.2 GNU make命令选项

GNU make 命令还有一些其他选项,下面给出了这些选项。

命令行选项 含义

-C DIR 在读取 makefile 之前改变到指定的目录 DIR。

-f FILE 以指定的 FILE 文件作为 makefile。

-h 显示所有的 make 选项。

-i 忽略所有的命令执行错误。

-I DIR 当包含其他 makefile 文件时,可利用该选项指定搜索目录。

-n 只打印要执行的命令,但不执行这些命令。

-p 显示 make 变量数据库和隐含规则。

-s 在执行命令时不显示命令。

-w 在处理 makefile 之前和之后,显示工作目录。

-W FILE 假定文件 FILE 已经被修改。

一、使用命令行的方式手动编译程序方法

1、利用文本编辑器创建hello.c文件

代码语言:javascript
复制
//hello.c
#include <stdio.h>
int main()
{
	printf("Welcome Emdoor!\n");
	return 1;
}

2、手动编译hello应用程序

在hello.c的目录的终端下输入:

代码语言:javascript
复制
  [root@local]$ gcc –c hello.c 
  [root@local]$ gcc hello.o –o hello  

通过ls命令查看当前目录下是否生成源代码 hello.c 的 object 文件 hello.o 和 可执行文件 hello,运行可执行文件hello,查看一下运行结果。

代码语言:javascript
复制
  [root@local]$ ./hello  

3、修改hello.c文件,重新手动编译应用程序。

4、删除hello.o和hello文件

代码语言:javascript
复制
[root@local]$ rm –f hello.o
[root@local]$ rm –f hello

二、利用 GNU make 自动编译应用程序方法

1、 利用文本编辑器创建一个makefile文件,并将其保存到与hello.c相同的目录下。

代码语言:javascript
复制
# makefile test for hello program
CC=gcc
CFLAGS=
all: hello
hello: hello.o
	$(CC) $(CFLAGS) hello.o -o hello
hello.o: hello.c
	$(CC) $(CFLAGS) -c hello.c -o hello.o
clean:
	rm –rf hello *.o

2、先后执行如下命令

代码语言:javascript
复制
[root@local]$ make
[root@local]$ ls
[root@local]$ ./hello

查看并记录所生成的文件和运行的结果。

3、执行make clean命令:

代码语言:javascript
复制
[root@local]$ make clean

4、修改hello.c文件,重复第2、3步操作,查看并记录所生成的文件和运行结果,并与手动编译进行比较,写出你的结论。

5、重新编辑makefile文件(斜黑体表示修改部分)

代码语言:javascript
复制
# makefile test for hello program
#written by Emdoor
CC=gcc
CFLAGS=
OBJS=hello.o
all: hello
hello: $(OBJS)
	$(CC) $(CFLAGS) $^ -o $@
hello.o: hello.c
	$(CC) $(CFLAGS) –c $< -o $@
clean:
	rm –rf hello *.o

6、重复第2,3步操作,查看并记录所生成的文件和运行的结果。比较这两种操作,写出你的结论。同时指出$^ 、$@、$<在上述Makefile中的含义。

三、多个.c文件的编译

1、创建文件hello1.c、hello2.c、hello.h和makefile

代码语言:javascript
复制
//hello1.c
#include <stdio.h>
int main()
{
	printf("Welcome Emdoor!\n");
	test2();
	return 1;
}
代码语言:javascript
复制
//hello2.c
#include "hello2.h"
#include <stdio.h>
void test2(void)
{
	printf("Welcome Emdoor! –hello2\n");
}
代码语言:javascript
复制
# makefile test for multi files program
CC=gcc
CFLAGS=
OBJS=hello1.o hello2.o
all: hello
hello: $(OBJS)
	$(CC) $(CFLAGS) $^ -o $@
hello1.o: hello1.c
	$(CC) $(CFLAGS) -c $< -o $@
hello2.o: hello2.c
	$(CC) $(CFLAGS) -c $< -o $@
clean:
	rm –rf hello *.o

2、先后执行如下命令

代码语言:javascript
复制
[root@local]$make
[root@local]$ls
[root@local]$./hello

查看并记录所生成的文件和运行的结果, 写出你的结论。

3、修改makefile文件(斜黑体表示修改部分)

代码语言:javascript
复制
# makefile test for multi files program
CC=gcc                                               
CFLAGS=
CFILES=$(wildcard *.c)
OBJS=$(CFILES:%.c=%.o)
all: hello
hello: $(OBJS)
	$(CC) $(CFLAGS) –o hello $(OBJS)
.c.o:
	$(CC) –c $<
clean:
	rm –rf hello *.o

4、重复第2步操作,查看并记录所生成的文件和运行的结果, 写出你的结论。并指出wildcard、.c.o的含义和变量CFILES代表的内容。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档