文章目录
- [一、使用Makefile 的引入](#一、使用Makefile 的引入)
- 二、Makefile的语法规则
- 三、Makefile中的变量
- 四、Makefile中的自动化变量
- 四、Makefile中伪目标
- 五、Makefile中条件判断
- 六、Makefile中函数的使用
一、使用Makefile 的引入
1.GCC的编译流程
1.预处理
2.编译
3.汇编和链接。
💦预处理就是展开所有的头文件、替换程序中的宏、解析条件编译并添加到文件中。
💦编译是将经过预编译处理的代码编译成汇编代码,也就是我们常说的程序编译。
💦汇编就是将汇编语言文件编译成二进制目标文件。
💦链接就是将汇编出来的多个二进制目标文件链接在一起,形成最终的可执行文件,链接的时候还会涉及到静态库和动态库等问题。
2.Makefile的引入
💦使用 GCC 编译器在 Linux 进行 C 语言编译,通过在终端执行 gcc 命令来完成 C 文件的编译,如果工程只有一两个 C 文件还好,需要输入的命令不多,当文件有几十、上百甚至上万个的时候用终端输入 GCC 命令的方法显然是不现实的。
💦如果我们能够编写一个文件,这个文件描述了编译哪些源码文件、如何编译那就好了,每次需要编译工程的时只需要使用这个文件就行了。所以就诞生了工程编译的工具:make,描述哪些文件需要编译、哪些需要重新编译的文件就叫做 Makefile。
💦Makefile 就跟脚本文件一样,Makefile 里面还可以执行系统命令。使用的时候只需要一个 make即可。
二、Makefile的语法规则
💦Makefile作为一个工具,有其自己的语法规则。
💦举例说明:创建main.c input.c calcu.c input.h calcu.h文件。
main.c
c
#include "input.h"
#include "calcu.h"
#include <stdio.h>
int main(int argc, char *argv[])
{
int a, b, num;
input_int(&a, &b);
num = calcu(a, b);
printf("%d + %d = %d\r\n", a, b, num);
}
input.c
c
#include <stdio.h>
#include "input.h"
void input_int(int *a, int *b)
{
printf("input two num:");
scanf("%d %d", a, b);
printf("\r\n");
}
calcu.c
c
#include "calcu.h"
int calcu(int a, int b)
{
return (a + b);
}
两个.h文件
c
input.h文件
#ifndef _INPUT_H
#define _INPUT_H
void input_int(int *a, int *b);
#endif
calcu.h文件
#ifndef _CALCU_H
#define _CALCU_H
int calcu(int a, int b);
#endif
💦输入编译指令gcc main.c input.c calcu.c -o main此指令编译生成可执行文件main
如下图所示:可以看到编译后是可以正常执行的。
💦从上图可以看出使用命令编译文件,只需要一行就可以完成编译,但是此时工程只有三个文件,如果有n个文件或者有一个文件被修改了,使用上面的命令编译的时候所有的文件都会重新编译,编译一次所需要的时间就可怕了。
💦所以最好的办法是哪个文件被修改了,就编译这个被修改的文件即可,其它没有修改的文件就不需要再次编译了,为此改变一下我们的编译方法,如果第一次编译工程,我们先将工程中的文件都编译一遍,然后修改了哪个文件就编译哪个文件,命令如下
💦上述命令前三行分别是将 main.c、input.c 和 calcu.c 编译成对应的.o 文件,所以使用了"-c"选项,"-c"选项是只编译不链接。最后一行命令是将编译出来的所有.o 文件
链接成可执行文件 main。假如我们现在修改了 calcu.c 这个文件,只需要将 caclue.c 这一个文件重新编译成.o 文件,然后在将所有的.o 文件链接成可执行文件即,只需要下面两条命令即可
💦此时看似解决了修改一个文件或者多个文件修改后编译和链接问题,但如果修改的文件一多,都不记得哪个文件修改过了,然后忘记编译,然后......,为此我们需要这样一个工具:
1、如果工程没有编译过,那么工程中的所有.c 文件都要被编译并且链接成可执行程序。
2、如果工程中只有个别 C 文件被修改了,那么只编译这些被修改的 C 文件即可。
3、如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的 C 文件,并且
链接成可执行文件。
所以引出makefile工具,在工程目录下创建名为"Makefile"的文件,文件名一定要叫做=="Makefile"==并且区分大小写的。
c
main:main.o input.o calcu.o
gcc main.o input.o calcu.o -o main
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
calcu.o:calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
💦命令解析:冒号前边为生成的目标文件,后边为生成目标的源文件,也就是说冒号后边的文件通过下边的指令生成冒号前的文件。main.o是main.c通过gcc -c main.c 指令生成的。
三、Makefile中的变量
1.全局变量
💦Makefile 中的变量都是字符串!类似 C 语言中的宏。使用变量将上面的代码修改,修改以后如下所示:
c
#main:main.o input.o calcu.o
#gcc main.o input.o calcu.o -o main
objects=main.o input.o calcu.o
main:$(objects)
gcc $(objects) -o main
main.o:main.c
gcc -c main.c
input.o:input.c
gcc -c input.c
calcu.o:calcu.c
gcc -c calcu.c
clean:
rm *.o
rm main
💦objects是全局变量,代表main.o input.o calcu.o三个文件,注释使用#号
2.赋值符"=",":=","?="区别
💦使用"="在给变量的赋值的时候,变量的真实值取决于它所引用的变量的最后一次有效值,加入@符号不显示执行过程。
💦使用":="在给变量的赋值的时候,变量的真实值取决于它所引用的变量的第一次有效值,加入@符号不显示执行过程。
💦使用"?="赋值符,如果变量 curname 前面没有被赋值,那么此变量就是"?="后边的值,如果前面已经赋过值了,那么就使用前面赋的值,相当于条件判断,如果前边定义过就是一定义,没有定义就是用条件后边的。
💦变量追加"+="
四、Makefile中的自动化变量
💦在模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完。
注意:自动化变量只出现在规则的命令中
💦 7 个自动化变量中,常用的三种: @ 、 @、 @、<和$^,我们使用自动化变量来完成上边的例子:
四、Makefile中伪目标
💦 Makefile 有一种特殊的目标------伪目标,一般的目标名都是要生成的文件,比如下边代码:main是要生成的目标文件,
c
main:$(objects)
💦 伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
比如:只需要执行clean下边的指令
c
clean:
rm *.o
rm main
💦 使用伪目标的目的:主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的。
💦 上述规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当我们输入"make clean"以后,后面的"rm *.o"和"rm main"总是会执行。如果我们在工作目录下创建一个名为"clean"的文件,那就不一样了,当执行"make clean"的时候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将 clean 声明为伪目标
代码如下:
c
.PHONY:clean
clean:
rm *.o
rm main
💦 声明 clean 为伪目标以后不管当前目录下是否存在名为"clean"的文件,输入"make clean"的话规则后面的 rm 命令都会执行。
五、Makefile中条件判断
💦 Makefile 也同C 语言一样支持条件判断语句,根据不同的情况来执行不同的代码。
六、Makefile中函数的使用
💦 Makefile 支持函数,类似 C 语言一样,与C语言不同的是,不支持自定义函数,Makefile 中的函数是已经定义好的,可以直接使用。如果有需要可以去网上搜索。