如要写文件a.c a.h b.c 三个文件
其中a.c里面使用了a.h里面的宏定义,调用了b.c里面定义的函数。要一起编译这个小工程的方法有:
1.gcc -o test a.c b.c a.h
这样执行可得到执行文件test ,然后运行即可,但是如果工程中文件很多的时候就不适合了。
2.编写makefile
在makefile文件中:
test:a.c b.c a.h
gcc -o test a.c b.c
注意:第二行是tab键按下所空余的。第一行左边为目标文件后面跟着冒号,右边为依赖文件,第二行为要执行的命令。
3.修改makefile:
test:a.o b.o
gcc -o test a.o b.o
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
注意:-c 指的是预处理,编译,汇编三步,但是第四步链接不执行。
如果a.c文件被修改了,则make之后会重新执行第二条语句,然后执行第一条语句,但第三条语句无关,则不会被执行。
问题仍然是工程中文件数目太多时,太麻烦。
4.继续修改makefile ,用到通配符等进行简化:
test:a.o b.o
gcc -o test a.o b.o
%.o:%.c
gcc -c -o $@ $<
注意:这里%号为通配符,这里同名的.o文件依赖于.c文件。命令中$@表示目标文件,$<表示第一个依赖文件,$^表示所有的依赖文件。
这里这样写有错误,当a.h文件被修改后,并不会 被发现而造成错误,还要补上: a.o:a.c a.h 才行。
5. 继续修改makefile,通过生成依赖文件来解决上面的问题。
test:a.o b.o
gcc -o test a.o b.o
%.o:%c
gcc -Wp,-MD,$@.d -c -o $@ $<
clean:
rm *.o test
注释:这里用到了gcc 的选项-Wp,-MD 起到的作用是生成一个依赖文件,如a.o 依赖于a.c和a.h,这样的话就不会遗漏其他的.h文件了,依赖文件中保存了相互的依赖关系。而不必像上面那样手动添加依赖关系了。这里生成的依赖文件的格式为$@.d,如目标文件为a.o,则对应的依赖文件为a.o.h,打开这个依赖文件就可以看见a.o依赖于a.h和a.c。
这里最后还加了clean这个假目标文件,以及删除.o 和test的命令。当执行make clean指令时,就会执行这句话。
补充知识:
1.我们把每一个长行使用反斜杠-新行法分裂为两行或多行,实际上它们相当于一行,这样做的意图仅仅是为了阅读方便。
2.CC 是一个全局变量,它指定你的Makefile所用的编译器,一般默认是gcc,你可以显示的指定,比如说 CC=gcc ,-c -o 都是gcc的参数,CC -o相当于在中端中执行gcc xx.c -o xx.o
3.目标‘clean’不是一个文件,仅仅是一个动作的名称。正常情况下,在规则中‘clean’这个动作并不执行,目标‘clean’也不需要任何依赖。一般情况下,除非特意告诉make执行‘clean’命令,否则‘clean’命令永远不会执行。注意这样的规则不需要任何依赖,它们存在的目的仅仅是执行一些特殊的命令。象这些不需要依赖仅仅表达动作的目标称为假想目标。
4.例如在系统中加入一个新的OBJ文件,我们很有可能在一个需要列举的地方加入了,而在另外一个地方却忘记了。我们使用变量可以简化makefile文件并且排除这种出错的可能。变量是定义一个字符串一次,而能在多处替代该字符串使用。在makefile文件中使用名为objects,OBJECTS,objs,OBJS,obj或OBJ的变量来代表所有OBJ 文件已是约定俗称。然后再每一个需要列举所有OBJ文件的地方写如$(objects),这个已经定义的变量的来代替即可。
5.编译单独的C语言源程序并不需要写出命令,因为make可以把它推断出来:make有一个使用‘CC –c’命令的把C语言源程序编译更新为相同文件名的OBJ文件的隐含规则。例如make可以自动使用‘cc -c main.c -o main.o’命令把‘main.c’编译 ‘main.o’。因此,我们可以省略OBJ文件的更新规则。如果C语言源程序能够这样自动编译,则它同样能够自动加入到依赖中。所以我们可在依赖中省略C语言源程序,进而可以省略命令。
6.在实际应用中,应该编写较为复杂的规则以防不能预料的情况发生。更接近实用的规则样式如下:
.PHONY: clean
clean:
-rm $(objects)
7.一个简单的文件名可以通过使用通配符代表许多文件。Make中的通配符和Bourne shell中的通配符一样是‘*’、‘?’和‘[…]’。例如:‘*.C’指在当前目录中所有以‘.C’结尾的文件。
字符‘~’在文件名的前面也有特殊的含义。如果字符‘~’单独或后面跟一个斜杠‘/’,则代表您的home目录。如‘~/bin’扩展为‘/home/bin’。 如果字符‘~’后面跟一个字,它扩展为home目录下以该字为名字的目录,如‘~John/bin’表示‘home/John/bin’。在一些操作系统(如ms-dos,ms-windows)中不存在home目录,可以通过设置环境变量home来模拟。
在目标、依赖和命令中的通配符自动扩展。在其它上下文中,通配符只有在您明确表明调用通配符函数时才扩展。
通配符另一个特点是如果通配符前面是反斜杠‘\’,则该通配符失去通配能力。如‘foo\*bar’表示一个特定的文件其名字由‘foo’、‘*’和‘bar’构成。
8.
当您定义一个变量时通配符不会扩展,如果您这样写:
objects = *.o
变量objects的值实际就是字符串‘*.o’。然而,如果您在一个目标、依赖和命令中使用变量objects的值,通配符将在那时扩展。使用下面的语句可使通配符扩展:
objects=$(wildcard *.o)
9.
通配符在规则中可以自动扩展,但设置在变量中或在函数的参数中通配符一般不能正常扩展。如果您需要在这些场合扩展通配符,您应该使用函数wildcard,格式如下:
$(wildcard pattern...)
可以在makefile文件的任何地方使用该字符串,应用时该字符串被一列在指定目录下存在的并且文件名和给出的文件名的格式相符合的文件所代替,文件名中间由空格隔开。如果没有和指定格式一致的文件,则函数wildcard的输出将会省略。注意这和在规则中通配符扩展的方式不同,在规则中使用逐字扩展方式,而不是省略方式(参阅上节)。
使用函数wildcard得到指定目录下所有的C语言源程序文件名的命令格式为:
$(wildcard *.c)
我们可以把所获得的C语言源程序文件名的字符串通过将‘.c’后缀变为‘.o’转换为OBJ文件名的字符串,其格式为:
$(patsubst %.c,%.o,$(wildcard *.c))
这里我们使用了另外一个函数:patsubst,详细内容参阅字符串替换和分析函数。
这样,一个编译特定目录下所有C语言源程序并把它们连接在一起的makefile文件可以写成如下格式:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这里使用了编译C语言源程序的隐含规则,因此没有必要为每个文件写具体编译规则。 ‘:=’是‘=’的变异。
10.VPATH:所有依赖的搜寻路径。如果一个作为目标或依赖的文件在当前目录中不存在,make就会在VPATH指定的目录中搜寻该文件。如果在这些目录中找到要寻找的文件,则就象这些文件在当前目录下存在一样,规则把这些文件指定为依赖。
11.连接库的搜寻目标:
对于连接库文件,目录搜寻采用一种特别的方式。这种特别的方式来源于个玩笑:您写一个依赖,它的名字是‘-|name’的形式。(您可以在这里写一些奇特的字符,因为依赖正常是一些文件名,库文件名通常是‘libname.a’ 的形式,而不是‘-|name’ 的形式。)
当一个依赖的名字是‘-|name’的形式时,make特别地在当前目录下、与vpath匹配的目录下、VPATH指定的目录下以及‘/lib’, ‘/usr/lib', 和 ‘prefix/lib'(正常情况为`/usr/local/lib',但是MS-DOS、MS-Windows版本的make的行为好像是prefix定义为DJGPP安装树的根目录的情况)目录下搜寻名字为‘libname.so'的文件然后再处理它。如果没有搜寻到‘libname.so'文件,然后在前述的目录下搜寻‘libname.a'文件。
例如,如果在您的系统中有‘/usr/lib/libcurses.a'的库文件,则:
foo : foo.c -lcurses
cc $^ -o $@
如果‘foo’比‘foo.c’更旧,将导致命令‘cc foo.c /usr/lib/libcurses.a -o foo'执行。
缺省情况下是搜寻‘libname.so' 和‘libname.a'文件,具体搜寻的文件及其类型可使用.LIBPATTERNS变量指定,这个变量值中的每一个字都是一个字符串格式。当寻找名为
‘-|name’的依赖时,make首先用name替代列表中第一个字中的格式部分形成要搜寻的库文件名,然后使用该库文件名在上述的目录中搜寻。如果没有发现库文件,则使用列表中的下一个字,其余以此类推。
.LIBPATTERNS变量
缺省的值是"‘lib%.so lib%.a'",该值对前面描述的缺省行为提供支持。您可以通过将该值设为空值从而彻底关闭对连接库的扩展。
12.假想目标:
假想目标并不是一个真正的文件名,它仅仅是您制定的一个具体规则所执行的一些命令的名称。使用假想目标有两个原因:避免和具有相同名称的文件冲突和改善性能。
如果您写一个其命令不创建目标文件的规则,一旦由于重建而提及该目标,则该规则的命令就会执行。这里有一个例子:
clean:
rm *.o temp
因为rm命令不创建名为‘clean’的文件,所以不应有名为‘clean’的文件存在。因此不论何时您发布`make clean'指令,rm命令就会执行。
假想目标能够终止任何在目录下创建名为‘clean’的文件工作。但如在目录下存在文件clean,因为该目标clean没有依赖,所以文件clean始终会认为已经该更新,因此它的命令将永不会执行。为了避免这种情况,您应该使用象如下特别的.PHONY目标格式将该目标具体的声明为一个假想目标:
.PHONY : clean
一旦这样声明,‘make clean’命令无论目录下是否存在名为‘clean’的文件,该目标的命令都会执行。
因为make知道假想目标不是一个需要根据别的文件重新创建的实际文件,所以它将跳过隐含规则搜寻假想目标的步骤(详细内容参阅使用隐含规则)。这是把一个目标声明为假想目标可以提高执行效率的原因,因此使用假想目标您不用担心在目录下是否有实际文件存在。这样,对前面的例子可以用假想目标的写出,其格式如下:
.PHONY: clean
clean:
rm *.o temp
13.自动生成依赖:
在为一个程序编写的makefile文件中,常常需要写许多仅仅是说明一些OBJ文件依靠头文件的规则。例如,如果‘main.c’通过一条#include语句使用‘defs.h’,您需要写入下的规则:
main.o: defs.h
您需要这条规则让make知道如果‘defs.h’一旦改变必须重新构造‘main.o’。由此您可以明白对于一个较大的程序您需要在makefile文件中写很多这样的规则。而且一旦添加或去掉一条#include语句您必须十分小心地更改makefile文件。
为避免这种烦恼,现代C编译器根据原程序中的#include语句可以为您编写这些规则。如果需要使用这种功能,通常可在编译源程序时加入‘-M’开关,例如,下面的命令:
cc -M main.c
产生如下输出:
main.o : main.c defs.h
这样您就不必再亲自写这些规则,编译器可以为您完成这些工作。
14.foreach函数:
例子:names
上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o
注意,foreach中的n参数是一个临时的局部变量,foreach函数执行完后,参数n的变量将不在作用,其作用域只在foreach函数当中。