一、准备C程序
1、input.h
#ifndef __INPUT_H
#define __INPUT_H
void input_int(int *a, int *b);
#endif
2、input.c
#include <stdio.h>
#include "input.h"
void input_int(int *a, int *b)
{
printf("input two nums: ");
scanf("%d %d", a, b);
printf("\r\n");
}
3、calcu.h
#ifndef __CALCU_H
#define __CALCU_H
int calcu(int a, int b);
#endif
4、calcu.c
#include "calcu.h"
int calcu(int a, int b)
{
return (a + b);
}
5、main.c
#include <stdio.h>
#include "input.h"
#include "calcu.h"
int main()
{
int a, b, num;
input_int(&a, &b);
num = calcu(a, b);
printf("%d + %d = %d\r\n", a, b, num);
return 0;
}
编译上述.c文件
gcc main.c calcu.c input.c -o main
执行生成的可执行文件main
./main
二、使用make工具和Makefile文件编译
有时候修改的文件过多,以至于想不起哪个文件被修改过,所以我们需要一个工具:
(1)如果工程没有编译过,那么工程中的所有.c文件都要被编译并且链接成可执行文件。
(2)如果工程中只有个别.c文件被修改了,那么只编译这些被修改的.c文件即可。
(3)如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的.c文件,并且链接成可执行文件。
故我们需要一个Makefile,在工程目录下创建名为"Makefile"的文件,文件名为"Makefile",注意区分大小写。
Makefile的格式
注:该图来源于陈皓《跟我一起写Makefile》
Makefile文件内容如下:
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
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
在所有 .c文件未被编译成 .o文件之前,我们可以通过make命令将所有的.c文件进行统一编译:
执行生成的可执行文件main
如果之后再对其中的某些程序进行修改,再次使用make命令,Makefile会帮助自动完成重新编译(根据最后修改日期是否变动,即使是多增加一个空格)
Makefile的规则
Makefile首先检查依赖文件是否都存在,若检查通过,则按照具体命令执行具体的规则。
以上图中的第一条规则为例,这条规则的目标是main,main.o、input.o和 calcu.o是生成main的依赖文件,如果要更新目标main,就必须要先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。
注意:命令列表中的每条命令必须以TAB键开始,不能使用空格!
make命令会为Makefile中的每个以TAB开始的命令创建一个Shell进程去执行。
首先更新第一条规则中的main,第一条规则的目标成为默认目标,只要默认目标更新了那么就认为Makefile的工作完成了。整个Makefile就是为了完成这个工作。在第一次编译的时候由于main还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o、input.o和 calcu.o这个三个.o文件,这三个.o文件目前还都没有,因此必须先更新这三个文件。make会查找以这三个.o文件为目标的规则并执行。(这可能就是老师上说的跟深度优先遍历有点像的原因吧hh~)
以main.o为例,发现更新main.o的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc -c main.c”,这行命令很熟悉了吧,就是生成main.o,其它两个.o文件同理。
最后一个规则目标是clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o文件以及可执行文件main,也就是说,clean的功能就是完成工程的清理,“make clean”的执行过程如下图所示。
总结一下make的执行过程:
(1)make命令会在当前目录下查找以Makefile(makefile其实也可以)命名的文件。
(2)当找到 Makefile文件以后就会按照 Makefile中定义的规则去编译生成最终的目标文件。(3)当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比
目标文件晚)的话就会执行后面的命令来更新目标。
以上就是make的执行过程,make工具就是在 Makefile中一层一层的查找依赖关系,并执行相应的命令。编译出最终的可执行文件。Makefile的好处就是“自动化编译”,一旦写好了Makefile文件,以后只需要一个make命令即可完成整个工程的编译,极大的提高了开发效率。
Makefile的变量
跟C语言一样,Makefile 也支持变量。先看一下前面的例子:
上述Makefile语句中,main.o、input.o和 calcu.o这三个依赖文件,我们输入了两遍,当然了,我们的这个Makefile 比较小,还是可以接受手动输入的,但是如果 Makefile复杂的时候这种重复输入的工作就会非常费时间,而且非常容易输错,为了解决这个问题,Makefile加入了变量支持。不像C语言中的变量有int、char等各种类型,Makefile 中的变量都是字符串!类似于C语言中的宏。
使用变量将上面的代码修改,修改以后如下所示:
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
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
再执行一下更新后的Makefile文件(注意,此处笔者将Makefile文件名改为了makefile,以此证明小写字母也是可以的hh~)
Makefile变量的赋值
(1)赋值符 "="
使用"="给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,例如:
name = qingshanke
curname = $(name)
name = cuizhuxuan
print:
echo curname: $(curname)
使用 make print 指令执行结果如下:
这说明 "=" 可以借助另一个变量,将变量的真实值推到后面去定义。
(2)赋值符 ":="
将上述例子中的 = 改为 :=,再次运行
name = qingshanke
curname := $(name)
name = cuizhuxuan
print:
echo curname: $(curname)
使用 make print 指令执行结果如下:
赋值符 ":=" 不会使用后面定义的变量,只能使用前面已经定义好的,这就是 = 和 := 二者的区别。
Makefile模式规则
main: main.o input.o calcu.o
gcc -o main main.o input.o calcu.o
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
上述Makefile中第3~8行是将对应的.c源文件编译为.o文件,每一个C文件都要写一个对应的规则,如果工程中C文件很多的话显然不能这么做。为此,我们可以使用Makefile中的模式规则,通过模式规则我们就可以使用一条规则来将所有的.c文件编译为对应的.o文件。
模式规则中,至少在规则的目标定义中要包含“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有以.c结尾的文件,类似于通配符,a.%.c就表示以a.开头且以.c结束的所有文件。
当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方法如下:
%.o:%.c
命令
注意:对于通配符而言,我们不能通过 -c %.c 来进行统一编译(如下图所示),因为-c后面需要准确的文件名,为此我们引入自动化变量。(正确的写法在后面会给出,莫慌hh~)
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o:%.c
gcc -c %.c
clean:
rm *.o
rm main
Makefile自动化变量
上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生成对应的目标呢?
自动化变量就是完成这个功能的。所谓自动化变量就是这种变量会把模式中所定义的一系列的文件自动的逐个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中,常用的自动化变量如下图所示。
其中$@,$<和$^是我们最常用的三种。
因此,使用通配符就需要这样写
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o:%.c
gcc -c $<
clean:
rm *.o
rm main
Makefile伪目标
Makefile有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行make命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
使用伪目标的主要是为了避免Makefile中定义的执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的,比如在前面有如下代码用来完成清理工程的功能。
clean:
rm *.o
rm main
如果不小心在当前工作目录中创建了一个clean文件(通过ls命令我们可以看出该文件已经存在),那么make clean指令将不再有效。
此时,我们可以通过指令.PHONY:clean 将clean文件设为伪目标,使得指令可以继续生效
objects = main.o input.o calcu.o
main: $(objects)
gcc -o main $(objects)
%.o:%.c
gcc -c $<
.PHONY:clean
clean:
rm *.o
rm main
本实验根据make工具和Makefile的使用_make makefile_受折磨的灵魂。的博客-CSDN博客
进行,相当于是对该博客实验的复现,非常感谢这位同学提供的实验例子,感谢感谢!