Makefile教程(二):示例剖析


本文参考文章:
Linux Make(Makefile)由浅入深的学习与示例剖析


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

1. 多文件编译的总体结构

如下图所示, 本示例共包含 float类型加法、加法头函数、int类型加法、main主函数、float类型减法、减法头函数、int类型减法
在这里插入图片描述
源代码如下:

//main.c
#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

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

/* 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

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

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

2. 方法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 运行结果
在这里插入图片描述
评析: 此方法遵照单文件编译方法,过程清晰、直观易懂;但效率很低,在编译文件数量很大或源文件修改时,此方法效率很低,且难以维护

3. 方法2 ( 多文件编译——使用makefile )

首先,了解一下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项目管理格式,现在我们将逐步对其进行优化、改进

4. 方法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 运行结果
在这里插入图片描述
评析: 方法3利用makefile变量,引入变量使makefile更加简洁、清晰,便于分组、统一维护,编译管理更加高效

5. 方法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命令自动推导依赖文件,来编译系统,高效但不太直观,高手可用

6. 方法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文件中,引入参数变量、命令变量和自动变量,此方法编译系统,高效但不太直观,特别是维护修改不便,高手可秀。

7. 方法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自动推导,非常简洁、高效,但不太直观,特别是具体文件依赖关系不清,维护较不便。


发布了88 篇原创文章 · 获赞 16 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/WHEgqing/article/details/104928527