Linux Make(Makefile)由浅入深的学习与示例剖析

2019-02-19 15:05:35 浏览数 (1)

经过长时间学习和研究linux GNU make工程管理器 ,现在把学习心得与大家分享一下,希望本文能教会您一些有用的东西。

make工具,是所有想在Linux/Unix系统上编程的用户都需要且必须掌握的工具。如果您写的程序没有用到make工具,则说明您写的程序仅仅是个人练习小程序,称不上有实用价值的程序,因此我们必须学习、掌握并灵活运用它。

在Linux/UNIX 系统中,习惯使用 Makefile或makfile 文件作为make命令目标文件。 Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互依赖关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。

一、多文件编译的总体结构

如下图所示, 本示例 共包含 float类型加法、加法头函数、int类型加法、main主函数、float类型减法、减法头函数、int类型减法

主函数

#include "add.h" #include "sub.h" #include <stdio.h> int main() { int x, y; float a, b; x=5; y=2; a=5.5; b=2.2; printf("%d %d = %d/n", x, y, add_int(x, y)); printf("%3.1f %3.1f = %3.1f/n", a, b, add_float(a, b)); printf("%d - %d = %d/n", x, y, sub_int(x, y)); printf("%3.1f - %3.1f = %3.1f/n", a, b, sub_float(a, b)); return 0; }

加法头函数

/* add.h */ #ifndef _ADD_H_ #define _ADD_H_ extern int add_int(int x, int y); extern float add_float(float x, float y); #endif

int类型加法

/* add_int.c */ int add_int(int x, int y) { return x y; }

float类型加法

/* add_float.c */ float add_float(float x, float y) { return x y; } ~

减法头函数

/* sub.h */ #ifndef _SUB_H_ #define _SUB_H_ extern int sub_int(int x, int y); extern float sub_float(float x, float y); #endif

int类型减法

/* sub_int.c */ int sub_int(int x, int y) { return x-y; }

float类型减法

/* sub_float.c */ float sub_float(float x, float y) { return x-y; }

二、方法1 ( 多文件编译

直接在Linux的Shell环境中,分别依次利用gcc -c *.c -o *.o命令进行编译,并链接生成可执行文件,具体做法如下:

依次编译上述文件:

编译后的结果文件:

然后,直接利用gcc -o main add_int.o add_float.o sub_int.o sub_float.o main.o命令,链接、编译成目标可执行文件main

最后,输入 ./main 运行结果

评析: 此方法遵照单文件编译方法,过程清晰、直观易懂;但效率很低,在编译文件数量很大或源文件修改时,此方法效率很低,且难以维护

三、方法 2 ( 多文件编译——使用makefile

此方法为了避免方法1的不足,利用Linux GNU make工程管理器,进行编译、管理源文件。

首先,了解一下make和makefile。 GNU make是一个工程管理器,专门负责管理、维护较多文件的处理,实现自动化编译。如果一个工程项目中,有成百上千个代码源文件,若其中一个或多个文件进过修改,make就需要能够自动识别更新了的代码,不需要像方法1一样逐个输入编译冗长的命令行,就可以完成最后的编译工作。make执行时,自动寻找makefile(Makefile)文件,然后执行编译工作。因此,我们需要自己编写makefile文件(Makefile与makefile都可以直接被make命令识别,下同。但Linux区分大小写)来管理、维护工程文件,提高实际项目的工作效率。

其次,需要注意Linux makefile(Makefile)文件的编写规范和方法:

1、需要由make工具创建目标体target,即通常的目标文件或可执行文件

2、声明并给出创建的目标体所依赖的文件(dependency-file)

3、编写完成创建每个目标体时所需要执行的命令(command)

具体格式如下:

target: dependency-file1    dependency-file2    dependency-file3    ...

command

target:规划的目标。通常是程序中间体或最后所需要生成的文件名,如 *.o或obj可执行文件的名称。此外,target目标也可以是make执行动作的名称,如clean等

dependency-file:规则的依赖。生成规则目标所需要的文件名列表,通常是一个目标依赖于一个或多个文件。

command:规则的命令。make程序所执行的的动作,可以为shell命令或者在shell下执行的程序。一个规则可以有多条命令,每条命令占一行。 在此特别需要注意的是每条命令行开始必须以Tab字符缩进开始,Tab缩进字符会告诉make命令此行是一个命令行,make按照命令完成此行相应的动作。这是在书写makefile(Makefile)文件时最易忽视和犯错的地方,而且大多比较隐蔽。

命令实质上市对任何一个目标的依赖文件发生变化后重建目标的动作描述。一个目标可以没有依赖而只有动作,即只有命令,如clean。此目标只有命令,没有依赖,主要作用是用来删除make过程中产生的中间文件(*.o),做收尾清理工作。

最后,上面均是纸上谈兵,现在我们来看具体实例,以直观、具体、详尽的解释makefile文件的编写方法和规则。

方法1可以用如下makefile文件代替,makefile编写如下:

# target: dependency-file main: main.o add_int.o add_float.o sub_int.o sub_float.o # NOTE: Tab before command gcc -o main main.o add_int.o add_float.o sub_int.o sub_float.o main.o: main.c add.h sub.h gcc -c main.c -o main.o add_int.o: add_int.c add.h gcc -c add_int.c -o add_int.o add_float.o: add_float.c add.h gcc -c add_float.c -o add_float.o sub_int.o: sub_int.c sub.h gcc -c sub_int.c -o sub_int.o sub_float.o: sub_float.c sub.h gcc -c sub_float.c -o sub_float.o clean: rm -f *.o main

说明:

#表示注释,其后的在编译预处理时,将被全部删除不执行

gcc -c 编译C语言源文件,编译生成目标文件 *.o

gcc -o 定义生成文件名称,可以为 *.o(目标文件)和 main(可执行文件)

rm -f *.o main 强制删去该目录下的所有*.o 目标文件和main可执行文件

在shell命令行执行make命令

查看make执行makefile文件后的编译结果如下:

与方法1的结果基本一致,并且直接生成了可执行文件main

最后,输入 ./main 运行结果

此方法,与方法1运行结果,完全一致!

评析: 方法2利用makefile文件,进行项目所有文件的编译管理,可保存、易修改,且编译执行效率高,大大减轻了每次编译的工作量

方法2,仅仅是最为初级的makefile项目管理格式,现在我们将逐步对其进行优化、改进

四、方法 3 (使用变量——改进1)

在编写makefile文件时,各部分引用变量的格式规范

1、 make变量引用不同于Linux Shell变量引用规则,而是需加括号,即 $(Var) 格式,无论 Var 是单字符变量名还是多字符变量名均可。

2、在命令行中出现的Shell变量,引用Shell的 $tmp 格式,一般为执行命令过程中的临时变量,不属于makefile变量,而是Shell变量。

3、对出现在命令行中的make变量,同样使用 $(Command) 格式来引用。

纸上得来终觉浅,绝知此事要躬行。输入vim makefile命令,在Shell 利用vim编辑器来编写makefile文件,具体写法如下:

OBJ=main.o add_int.o add_float.o sub_int.o sub_float.o make: $(OBJ) gcc -c main.c -o main.o add_int.o: add_int.c add.h gcc -c add_int.c -o add_int.o add_float.o: add_float.c add.h gcc -c add_float.c -o add_float.o sub_int.o: sub_int.c sub.h gcc -c sub_int.c -o sub_int.o sub_float.o: sub_float.c sub.h gcc -c sub_float.c -o sub_float.o clean: rm -f $(OBJ) main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1和方法2运行结果,完全一致!

评析: 方法3利用makefile变量,引入变量使makefile更加简洁、清晰,便于分组、统一维护,编译管理更加高效

五、方法 4 (使用自动推导——改进2)

编写makefile文件,让make命令自动推导。只要make看到了 *.o 文件,它就会自动把与之对应的 *.c 文件加到依赖文件中,并且gcc -c *.c 也会被推导出来,所以makefile就简化啦。 此外,我们使用 $(Command) 格式,来引用命令变量。具体做法如下

首先,在Shell输入 vim makefile ,利用VIM编辑makefile文件内容

CC=gcc OBJ=main.o add_int.o add_float.o sub_int.o sub_float.o make: $(OBJ) $(CC) -o main $(OBJ) main.o: add.h sub.h add_int.o: add.h add_float.o: add.h sub_int.o: sub.h sub_float.o: sub.h PHONY: clean clean: rm -f $(OBJ) main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1、方法2和方法3的运行结果,完全一致!

评析: 方法4在makefile文件中,引入参数变量和命令变量,利用make命令自动推导依赖文件,来编译系统,高效但不太直观,高手可用

六、方法5 (使用自动变量($^ $< $@)——改进3)

在编写makefile文件中,有三个非常有用的变量,即分别是 $@     $^     $<    其代表的具体意义如下:

$@  : 目标文件

$^   : 所有依赖文件

$<   : 第一个依赖文件

具体使用方法如下例所示

首先,在Shell输入 vim makefile ,利用VIM编辑makefile文件内容

CC=gcc OBJ=main.o add_int.o add_float.o sub_int.o sub_float.o make: $(OBJ) $(CC) -o $@ $^ main.o: main.c add.h sub.h $(CC) -c $< add_int.o: add_int.c add.h $(CC) -c $< add_float.o: add_float.c add.h $(CC) -c $< sub_int.o: sub_int.c sub.h $(CC) -c $< sub_float.o: sub_float.c sub.h $(CC) -c $< PHONY: clean clean: rm -f $(OBJ) main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1、方法2、方法3和方法4的运行结果,完全一致!

评析: 方法5在makefile文件中,引入参数变量、命令变量和自动变量,此方法编译系统,高效但不太直观,特别是维护修改不便,高手可秀。

七、方法6  (使用缺省规则(..c.o:)—— 改进4)

 在依次使用了上述变量、自动推导、自动变量规则后,或许还有人认为太复杂,想寻求更简洁的方法,这里我们再介绍makefile缺省规则。

 makefile的缺省规则如下:

..c.o:

gcc -c $<

这个规则表示,所有的 *.o 目标文件都是依赖于相应的 *.c 源文件的, 例如 main.o 依赖于 main.c 。 具体makefile编写方法如下

CC=gcc main: main.o add_int.o add_float.o sub_int.o sub_float.o $(CC) -o $@ $^ ..c.o: $(CC) -c $< clean: rm -f *.o main

然后,在shell命令行执行make命令

最后,输入 ./main 运行结果

此方法,与方法1、方法2、方法3、方法4和方法5的运行结果,完全一致!

评析: 方法6在makefile文件中,引入缺省规则,是make自动推导,非常简洁、高效,但不太直观,特别是具体文件依赖关系不清,维护较不便。

==================================================================================

综合评析: 以上方法,由浅入深,点评剖析,重在灵活运用。方法2直观易懂,方法3引入变量简洁,这两种方法便于管理维护,推荐使用。方法4、方法5和方法6,主要是深入剖析makefile博大精深的编写使用方法,在具体项目管理实践中,可以选择借鉴使用,适合内功深厚者。

以上示例程序,均已测试并运行通过 ,具体测试编译环境如下:

Linux系统: Red Hat Linux Server 5.2

VIM编辑器:VIM - Vi IMproved 7.0

系统的环境:Linux安装在VMWare 7.0 虚拟机上

==================================================================================

编译Bug与Debug小结

1、makefile: 4:   *** 遗漏分隔符 。 停止 。 错误提示,如下图

分析与处理: 以上错误提示,说明makefile文件第4行,分隔符格式不正确,导致错误。错误详见下图

从上图可见,第4行为command命令行,应该如上述方法2中强调所说,命令行应当Tab分隔缩进 ,解决后如下图所示:

2、make: main 是最新的。 错误提示,如下图

这是因为该文件目录中,已经存在了目标可执行文件 main ,请见下图

解决办法:输入 rm main 或者 rm -f main 命令,先删去 main 文件,然后再输入 make 命令,进行编译链接即可

编译链接成功后,直接利用 ./main 运行生成的目标可执行文件即可啦 ^_^

0 人点赞