家人们,我问大家一个问题,我们之前的教程都是针对一个源文件 douya.c 进行了无数种操作。当我们有多个文件时,怎么链接到一起最后形成一个执行文件呢?
当然我们也有简单的做法。
我们给出实例,我们创建两个 c 文件,一个 douya.c 和 max.c,douya.c 里 main 函数调用 max.c 里的 max 比较大小函数:
给出 max.c 内容:
#include
#include "max.h"
int max(const int a, const int b){
return a > b ? a : b;
}
然后给出头文件 max.h,我们 extern 出 max 函数:
#ifndef MAX_H
#define MAX_H
extern int max(const int a, const int b);
#endif
最后我们给出 douya.c 内容:
#include
#include "max.h"
int main(){
printf("Hello, Douya!\n");
int result = max(4, 6);
printf("the max value is %d: \n", result);
return 0;
}
1.好了,这里我们给出了一个简单例子,主要是为了说明多文件的编译、链接、执行。
然后我们输入
$ gcc -Wall douya.c max.c -o douya
该命令自动将两个文件编译、链接并生成执行文件,运行 ./douya
$ ./douya
Hello, Douya!
the max value is 6:
我们就实现了。
2.当然我们也可以单文件先各自生成目标文件,最后链接成可执行文件。
如下:
$ gcc -c douya.c -o douya.o
这里的 -c 就表示只执行到汇编这一步而不链接,生成目标文件 douya.o;max.c 文件同理生成 max.o,最后我们将两个目标文件链接起来生成可执行文件。大家关于程序编译的四个步骤,你可没忘吧?
$ gcc -c douya.c -o douya.o
$ gcc -c max.c -o max.o
$ gcc max.o douya.o -o douya
$ ./douya
Hello, Douya!
the max value is 6:
肯定有家人会说了,一个工程项目往往几十个源文件,难道还要一一编译再链接?那也太麻烦了吧!
确实很麻烦,所以我们引入今天的正题。
我们要引出一个重要内容——Makefile。Makefile 是一种常用于编译的脚本语言。它可以更好更方便的管理你的项目的代码编译,节约编译时间(没改动的文件不编译)。
makefile 关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如:Delphi 的 make,Visual C++的 nmake,Linux 下 GNU 的 make。可见,makefile 都成为了一种在工程方面的编译方法。
我们下面介绍它的常用语法。
目标...: 依赖...
命令1
命令2
...
1.目标:目标即要生成的文件。如果目标文件的更新时间晚于依赖文件的更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则重新编译并更新目标。
2.依赖:即目标文件由哪些文件生成。如果依赖条件中存在不存在的依赖条件,则会寻找其它规则是否可以产生依赖条件。
例如:规则一是生成目标 hello.out 需要使用到依赖条件 hello.o,但是 hello.o 不存在。则 Makefile 会寻找到一个生成 hello.o 的规则二并执行。
3.命令:即通过执行该命令,由依赖文件生成目标文件。
注意每条命令前必须有且仅有一个 tab 保持缩进,这是语法要求。
4.ALL:Makefile 文件默认只生成第一个目标文件即完成编译,但是我们可以通过 “ALL” 指定需要生成的目标文件。
5.示例:我们前面讲了两个文件链接的实例,直接使用 Makefile 的方式编译:写一个 Makefile
ALL:douya
douya:max.o douya.o
gcc max.o douya.o -o douya
max.o:max.c
gcc -c max.c -o max.o
douya.o:douya.c
gcc -c douya.c -o douya.o
编译并执行:
$ make
gcc -c max.c -o max.o
gcc -c douya.c -o douya.o
gcc max.o douya.o -o douya
$ ./douya
Hello, Douya!
the max value is 6:
怎么样,每次我们编译,只需要 make 一下,不用再重复输入指令,是不是很方便?
1.wildcard。wildcard 用来明确表示通配符。
示例:
SRC = $(wildcard ./*.c)
表示匹配目录下所有的 .c 文件,并将其赋值给 SRC 变量。
2.patsubst。pat 是 pattern 的缩写,subst 是 substring 的缩写。Patsubst 是模式字符串替换函数。
示例:
OBJ = $(patsubst %.c, %.o, $(SRC))
这个函数有三个参数,意思是取出 SRC 中所有的值,然后将 “.c” 替换为 “.o”,最后赋值给 OBJ 变量。
这两个函数很有用,我们来举个例子。通过上面两个函数,加入我们目录下有很多个 “.c” 后缀的源文件,就不需要写很多条规则语句了,而是可以像下面这样写:
SRC = $(wildcard ./*.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: douya
douya: $(OBJ)
gcc $(OBJ) -o douya
$(OBJ): $(SRC)
gcc -c $(SRC) -o $(OBJ)
这里需要先将所有的 “.c” 文件编译为 “.o” 文件,这样后面更改某个 “.c” 文件时,其它的 “.c” 文件将不再编译,而只是编译有更改的 “.c” 文件,可以大大节约大项目中的编译速度。
Makefile 是可以使用 shell 命令的,所以 shell 支持的通配符在 Makefile 中也是同样适用的。 shell 中使用的通配符有:"*","?","[...]"
。具体看一下这些通配符的表示含义和具体的使用方法。
*
: 匹配 0 个或者是任意个字符
?
: 匹配任意一个字符
[]
: 我们可以指定匹配的字符放在"[]"
中
下面实例可以说明通配符可以使用在规则的命令当中,表示的是任意的以 .o 结尾的文件。
.PHONY:clean
clean:
rm -rf *.o test
下面实例可以说明我们的通配符不仅可以使用在规则的命令中,还可以使用在规则中。用来表示生所有的以 .c 结尾的文件。
test:*.c
gcc -o $@ $^
还有一个和通配符 "*"
相类似的字符,这个字符是 "%"
,也是匹配任意个字符,使用在我们的的规则当中。
test:test.o test1.o
gcc -o $@ $^
%.o:%.c
gcc -o $@ $^
Makefile 中也有一些已经定义好的常用变量,这里介绍其中常用的3个。
(1)$@
:表示规则中的目标,例如 douya.o
(2)$<
:表示规则中的第一个依赖条件,例如 douya.c
(3)^
:表示规则中的所有依赖条件,由于我们示例中都只有一个依赖条件,这种情况下 ^
和 $<
区别不大。
(4)示例:使用这些变量替换上面写的 Makefile,即是:
ALL: douya
douya: douya.o max.o
gcc -g $^ -o $@
%.o: %.c
gcc -c $^ -o $@
1.代码清理 clean。我们可以编译一条属于自己的 clean 语句,来清理 make 命令所产生的所有文件。例如:
ALL: douya
douya: douya.o max.o
gcc -g $^ -o $@
%.o: %.c
gcc -c $^ -o $@
clean:
-rm -rf *.o douya
这样我们就可以使用 clean 命令来清理生成的文件了:
$ make clean
2.伪目标 .PHONY。上面我们写了一个 clean 语句,使得我们执行 “make clean” 命令的时候,可以清理我们生成的文件。
但是假如还存在一个文件名就是 clean 文件,那么我们再执行 “make clean” 命令的时候就只是显示:make: clean is up to date.
解决方法就是我们使用伪目标,这样就可以避免出现上面的问题了,例如:
ALL: douya
douya: douya.o max.o
gcc -g $^ -o $@
%.o: %.c
gcc -c $^ -o $@
clean:
-rm -rf *.o douya
.PHONY: clean ALL
我们只是讲了入门知识,以上知识已经足以帮助我们看懂别人写好的 Makefile,或者自己写简单的 Makefile。
关于 Makefile,深入学习请参考《跟我一起写 makefile pdf 陈皓》
Makefile 是一种常用于编译的脚本语言。它可以更好更方便的管理你的项目的代码编译,节约编译时间(没改动的文件不编译)。
makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。
make 命令根据 Makefile 中的规则和依赖关系,自动构建和编译项目的源代码,以生成可执行文件或其他目标。
目标是要构建的文件或任务,依赖关系是构建目标所需的文件或任务列表。Makefile 规则定义了如何从依赖关系生成目标。