makefile与c语言的学习(二)

(一)makefile的书写规则
(1)依赖关系
(2)命令

foo.o:foo.c def.h
	cc -c -g foo.c

(其中 -g 是 gdb 做调试的命令)
简单介绍gcc 命令
1、预处理
在预处理阶段,编译器主要作加载头文件、宏替换、条件编译的作用

gcc -E main.c -o main.i

2、编译
在编译过程中,编译器主要作语法检查和词法分析。在确认所有指令都符合语法规则之后,将其翻译成等价的中间代码或者是汇编代码。

gcc -S main.i -o main.s

3、汇编
汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码。

gcc -c main.s -o main.o

as -c main.s -o main.o

4、链接
在成功编译之后,就进入了链接阶段。链接就是将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。
注意:如果直接调用binutils 中的ld进行链接,会报出错误。(由于单独目标文件无法生成一个完整的可执行文件,还需要指出各种依赖库、引导程序和链接脚本)

ld main.o -o main
gcc main.o -o main
-c               Compile and assemble, but do not link.
-o <file>        Place the output into <file>.
                 'none' means revert to the default behavior of guessing the language based on the file's extension.

gcc不加任何参数就可以直接编译生成可执行文件,生成可执行文件a.out

gcc main.c

targets 是文件名,以空格分开,可以使用通配符。command 是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab 键]开头, 如果和 prerequisites 在一行, 那么可以用分号做为分隔。如果命令太长,你可以使用反斜框(‘\’)作为换行符。

(二)如何在规则中使用通配符
make 支持三种通配符 :“*”,“?”和“[…]”
而波浪号代表$HOME目录;
第一个通配符代替了一系列的文件

objects = *.o

此处变量的值就是“*.o”,如果要让通配符在变量中展开,也就是让 objects 的值是所有[.o]的文件名的集合。

objects := $(wildcard *.o)

(三)文件搜寻
当make需要寻找文件的依赖关系时,可以在文件前加上路径,可以通过特殊变量“VPATH”**如果没有指明这个变量,make只会在当前的目录中去寻找依赖文件和目标文件。**如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src (第一个目录): …/headers(第二个目录)
两个目录src 与 …/headers 分别由冒号分隔。
或者
使用关键字vpath
1、vpath < pattern> < directories>
为符合模式< pattern >的文件指定搜索目录< directories>。
2、vpath < pattern>
清除符合模式< pattern>的文件的搜索目录。
3、vpath
清除所有已经设置好的文件搜索目录。

注意:vpath 中的< pattern>需要包含“%”字符。例如”%.h"表示所有以“.h"结尾的文件。即< pattern>指定了搜索的文件集,< directories>指定了< pattern>的文件集的搜索的目录。
如果连续的vpath语句中出现了相同< pattern>,make 会按照vpath 语句的先后顺序来执行搜索。

vpath %.c foo
vpath % ../headers
vpath %.c bar

.c文件,从foo ->…/headers -> bar
如果是这种情况:

vpath %.c foo:bar
vpath % ../headers

.c文件,从foo ->bar ->…/headers

(四)伪目标
以clean为例,并不生成“clean"这个文件,”伪目标“并不是文件,而是标签,只有通过显示指明这个目标才能让其生效,为了使”伪目标“的取名不能和文件名重名。使用一个特殊标记”.PHONY"来指明一个目标为“伪目标”,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY:clean
clean:
	rm *.o temp

但是伪目标同样可以作为“默认目标”,只要将其放在第一个;一个示例,如果想通过makefile 输入make 生成若干个可执行文件,并且,所有的目标文件都写在一个makefile。

all:prog1 prog2 
.PHONY:all
prog1:prog1.o util.o
	gcc -o prog1 prog1.o util.o

prog2:prog2.o
	gcc -o prog2 prog2.o	

makefile 中的第一个目标会被作为默认目标,声明一个“all”的伪目标,其依赖于其他二个目标。
所以,伪目标同样也可以成为依赖

.PHONY : cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
	rm program

cleanobj :
	rm *.o

cleandiff :
	rm *.diff

(五)多目标
如果多个目标同时依赖于一个文件,我们就可以将其合并起来。如果多个目标的生成规则的执行命令是同一个,可以使用一个自动化变量“ $@"

bigoutput littleoutput : text.g
	generate text.g -$(subst output,,$@) > $@

其中 -$ (subst output,$ @)中的“$”表示执行一个 Makefile 的函数,函数名为 subst,后面的为参数。“ $@”表示目标的集合,就像一个数组,“ $@”依次取出目标,并执于命令。

bigoutput :text.g
	generate text.g -bit > bigoutput
littleoutput : text.g
	generate text.g -little > littleoutput

静态模式
语法:

<targets...>:<target-pattern>:<prereq-patterns...>
<commands>

targets定义了一系列的目标文件,可以有通配符
target-pattern 指明targets的模式
prereq-pattern 指明目标的依赖模式
(如前文相同,目标模式和依赖模式都应该有"%",来指明模式)

objects = foo.o bar.o
all: $(objects)
$(objects): %.o : $.c
	$(CC) -c $(CFLASS) $< -o $@

“%.o"表示< target-pattern> ;”%.c" 表示< prereq- patterns>;命令中的"$<"和“ $@“都是自动化变量,” $<"表示所有的依赖目标集(foo.c bar.c),” $@"表示目标集。
等价于

foo.o : foo.c
	$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
	$(CC) -c $(CFLAGS) bar.c -o bar.o

(六)自动生成依赖性
在makefile中,依赖关系可能包含一系列头文件,而大多数的C/C++编译器都支持一个"-M"的选项,自动寻找源文件的头文件,并生成一个依赖关系。

cc -M main.c

自动生成的依赖关系:

main.o : main.c defs.h

但是如果使用GNU的C/C++编译器,就得用“ -MM”参数。否则“ -M”参数会将一些标准库的头文件也包含进来。
(可以为每一个源文件的自动生成的依赖关系放在一个文件中,为每一个“name.c”的文件生成一个“name.d"的makefile文件。于是可以写出.c文件与.d文件的依赖关系,并让make自动更新.d文件,并包含在makefile中)

(七)书写命令
规则中的命令与shell中的命令是一致的,make 会按照顺序执行命令,每条命令开头必须以【tab】键开头。除非是与依赖规则同一行并用分号隔开。(在命令行之间的空格或者空行会被忽略,如果该空格或空行是以Tab键开头,make 会认为是一个空命令)
make的命令默认是由/bin/sh -即shell命令解释执行
(1)显示命令
用”@“字符在命令行前,那么,这个命令将不被make显示出来。
如:@echo 正在编译XXX模块

当make执行时,会输出”正在编译XXX模块“字串,但不会输出命令。即如果没有@,那么make执行就会输出:
echo 正在编译XXX模块 (显示多余)
正在编译XXX模块

如果make执行时,带入make参数”-n“,那么只显示命令。但不会执行命令。(可以检查makefile 执行起来是声明顺序)

make参数“-s”则是全面禁止命令显示
(2)命令执行
当依赖目标新于目标时,即规则的目标需要被更新,make会一条条的执行之后的命令,所以注意,如果需要让上一条的命令结果应用于下一条命令,需要将两条命令写在一行,并用分号隔开。
例如:此时执行make exec 时,cd命令没有作用,pwd会打印出当前的makefile目录。

exec:
	cd /home/desktop
	pwd

如果需要打印cd命令下的目录,此时pwd就会打印出“/home/desktop”

execcd /home/desktop ; pwd

(3)命令出错
当命令运行结束,make会检测每个命令的返回码,如果命令返回成功,make会执行下一条命令。如果一个规则中某个命令出错(退出码非0),那么make就会终止当前规则,但是有时命令出错并不表示就是错误的,为了忽略错误:
可以在 Makefile 的命令行前加一个减号“-”(在 Tab 键之后),标记为不管命令出不出错都认为是成功的。

make 加上“-i”参数,那么makefile中所有命令都会忽略错误。

而make加上“-k”参数,如果发生错误,终止当前规则执行,但会执行其他规则。
(4)嵌套执行make
在一些大工程中,可以在不同的目录都书写一个makefile,这样对于模块编译和分段编译有很大的好处。
例如:子目录下subdir,这个目录下有makefile文件,来指明这个目录下文件的编译规则,那么主makefile 应该写成

subsystem
	cd subdir && $(MAKE)

或者

subsystem
	$(MAKE) -C subdir

其中 $(make)宏变量,表示make 的参数
主makefile的变量可以传递到下级的makefile中,但不会覆盖下层的makefile所定义的变量,除非指定“-e”参数
1、传递变量至下级makefile中

export <variable>

2、不让变量传递至下级makefile

unexport <variable>

如果需要传递所有变量,只需一个export即可。
注意:两个变量,SHELL与MAKEFLAGS ,总是要传递到下层的makefile中,特别时MAKEFILES变量,其中包含了make的参数信息。如果在执行主makefile时有make 参数或在上层makefile中定义了这个变量,那么MAKEFILES会将这些参数,传递至下层makefile中。
(5)定义命令包
如果makefile中出现一些相同的命令序列,那么可以为相同的命令序列定义一个变量,以“define”开始,“endef”结束。

define run-yacc
	yacc $(firstword $^)
	mv y.tab.c $@
endef

"run-yacc"是这个命令包的名字,不要和makefile中的变量重名。
第一个命令是运行yacc 程序,由于yacc程序会生成”y.tab.c"文件
第二个命令就是修改文件名
示例执行命令包:

foo.c : foo.y
	$(run-yacc)

其中“$^”就是“foo.y"," $@“就是”foo.c”

发布了54 篇原创文章 · 获赞 4 · 访问量 1044

猜你喜欢

转载自blog.csdn.net/buzhiquxiang/article/details/103464068