0.程序的编译过程
(1)预处理
C/C++源文件中,以"#“开头的命令被称为预处理命令,如包含命令、宏定义命令、条件编译命令等。预处理就是将要包含的文件插入到源文件中、宏定义展开、根据条件编译命令选择使用的代码,最后将这些代码输入到一个”.i"文件中等待进一步处理,预处理用到arm-linux-cpp工具。
(2)编译
把C/C++代码(或者.i)翻译成汇编,所用到的工具是cc1.
(3)汇编
汇编是将第二步输出的汇编代码翻译成一定格式的机器码,LINUX系统上一般表现为ELF文件(OBJ文件),用到的工具是arm-linux-as。
(4)连接
连接就是将上一步生成的OBJ文件和系统库的OBJ文件、库文件连接起来,最终生成可以在特定平台运行的可执行文件,用到的工具是arm-linux-ld。
1.Makefile基础
makefile基础知识
1、赋值符号的区别:
-
= 是最基本的赋值,用到了之后才赋值,不能在变量后追加内容
-
:= 是覆盖之前的值,立即赋值,可以在变量后追加内容
-
?= 是如果没有被赋值过就赋予等号后面的值
-
+= 是添加等号后面的值
2、自动变量:
- $< 第一个依赖文件的名称
- $? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚
- $@ 目标的完整名称
- $^ 所有的依赖文件,以空格分开,不包含重复的依赖文件
3、常用的函数:
- $(patsubst %.c,%.o,x.c.cbar.c)
把字串“x.c.cbar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”
- $(filter <pattern…>,< text> )
以模式过滤字符串中的单词,保留符合模式的单词。可以有多个模式。
-
$(filter-out <pattern…>,< text> )
找到< pattern>中所有包含< text>的变量 -
$(foreach < var>,< list>,< text> )
把参数< list>中的单词逐一取出放到参数< var>所指定的变量中,然后再执行< text>所包含的表达式。每一次< text>会返回一个字符串,循环这个过程。 -
shell函数,例如files := $(shell echo *.c)
2.Makefile
1.规则
目标:依赖1 依赖2
命令
test: a.c b.c a.h
gcc -o test a.c b.c
a.“依赖”文件 比 “目标”文件 新
b.没有“目标”文件
2.优化
(1)每次执行上面一条Makefile,都会把所有的源文件执行一遍,不普适
test: a.o b.o #依赖于两个目标文件
gcc -o test a.o b.o
a.o: a.c a.h
gcc -c -o a.o a.c
b.o: b.c
gcc -c -o b.o b.c
(2)通配符:文件越多,不能一个一个写,改成通配符
test: a.o b.o
gcc -o test a.o b.o
a.o:a.c a.h #当a.h改变时,会重新编译a.c
%.o: %.c
gcc -c -o $@ $<
(3)生成依赖文件:以上缺点是所有.o的依赖文件都需要一个一个去写,gcc中有自动生成依赖的选项,保存在a.d文件中
gcc -c -o a.o a.c -Wp,-MD,a.d
再改Makefile
test:a.o b.o
gcc -o test a.o b.o
%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
(4)利用自动生成的依赖文件:第一次执行的时候会生成 %.o.d 文件,这个文件中包含对应.c源文件的依赖关系,这个依赖关系的格式也是Makefile格式的,如下所示
所以,把这个文件包含到主Makefile中,就会把对应.o文件的依赖关系也加进去,如下:
objs := a.o b.o
test :$(objs)
gcc -o test $^
dep_files := $(foreach f,$(objs), .$(f).d) #dep_files = 所有目标文件名.d ,即a.o.d b.o.d
dep_files := $(wildcard $(dep_files)) #用wildcard函数来看dep_files中所存在的文件,然后再赋值给他自己
ifneq($(dep_files) , ) #如果dep_files不为空的话,就代表编译过,生成过依赖文件了
include $(dep_files) #就包含这些依赖文件
endif
%.o: %.c #第一次执行的时候没有依赖文件,只依赖于.c文件,编译过一次后,就会有依赖文件,之后就会依赖于依赖文件
gcc -Wp,-MD,.$@.d -c -o $@ $<
clean:
rm *.o test
3.通用的Makefile
用法:每次使用时,只需要将Makefile和Makefile.build拷贝到需要的工程里,然后更改Makefile中编译和连接的FLAGS(CFLAGS/LDFLAGS),然后各子目录中创建Makefile,增加需编译的目标文件即可。
框架如下图所示,碰到目录先进入目录,直到进入最里面的目录,编译出built_in.o,再返回上一级目录,与该目录下的.o文件共同生成built_in.o文件,直到返回顶层目录,将所有文件夹下的built_in.o文件共同编译成built_in.o文件。
- 顶层目录的Makefile
- 顶层目录的Makefile.build
- 各级子目录的Makefile
(1)各级子目录的Makefile
obj-y += file.o #表示把当前目录下的file.c编进程序里
obj-y += subdir/ #表示要进入subdir这个子目录下去寻找文件,然后编进程序里,哪些文件由subdir目录中的Makefile决定。
#subdir后的 / 不可省略
(2)顶层目录的Makefile
- 定义obj-y来指定根目录下要编进程序去的文件、子目录
- 定义工具链、编译参数、链接参数,就是文件中用export导出的各变量,给Makefile.build用的
CROSS_COMPILE = #定义交叉编译工具,arm-linux- , 如果不用交叉编译,就不填
AS = $(CROSS_COMPILE)as #指定汇编工具
LD = $(CROSS_COMPILE)ld #指定连接工具
CC = $(CROSS_COMPILE)gcc #指定编译工具
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP AR NM #输出给其他文件用
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -O2 -g #设置一些编译时的选项(-Wall:打开所有警告;-g:产生调试信息,GBD可使用;-O2:多优化一些,增加了编译时间,提高生成代码运行效果)
CFLAGS += -I $(shell pwd)/include -I /usr/include/freetype2 #设置一些头文件的路径,Makefile换工程使用时,根据需要改一下这个
LDFLAGS := -lm -lfreetype -lvga -lvgagl #设置连接时的一些库,Makefile换工程使用时,根据需要改一下这个
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd) #把当前目录设置为顶层目录,主要是给各级编译时,需要用到顶层目录的Makefile.build
export TOPDIR
TARGET := show_file #设置最终生成的目标名字
obj-y += main.o #顶层目录下需要编译的文件及目录
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
all :
make -C ./ -f $(TOPDIR)/Makefile.build #递归文件夹执行;-C表示有子目录,先进子目录执行;-f表示后面的名字虽不是Makefile,但是当作Makefile来用。
#这句的意思就是在 “从当前目录,一直到obj-y及下级obj-y中所包含的” 所有目录下执行Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o #上一条已经把各级Makefile中加入的文件(包括顶层目录的main.o)统共编译成一个built-in.o,这里直接根据这个文件连接成目标可执行文件
clean:
rm -f $(shell find -name "*.o") #删除所有.o文件
rm -f $(TARGET) #删除生成的目标文件
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d") #删除生成的依赖文件
rm -f $(TARGET)
(3)顶层目录的Makefile.build
PHONY := __build
__build: # make时,进来首先执行这个
obj-y :=
subdir-y :=
include Makefile #包含当前目录的Makefile,这里面存着需要在当前目录编译的目标文件
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) #找到当前目录中,所有%/格式的文件,再把/符号去掉,作为变量传给__subdir-y
subdir-y += $(__subdir-y) # subdir-y = __subdir-y
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) #各个下级目录下编译成的built-in.o,放到这个subdir_objs变量中
cur_objs := $(filter-out %/, $(obj-y)) #从当前目录下得到当前目录需要编译的源文件
dep_files := $(foreach f,$(cur_objs),.$(f).d) #依赖文件,把当前目录下所有的.o文件加上.d变成 %.o.d 赋值给dep_files
dep_files := $(wildcard $(dep_files)) #dep_files 从这个变量中找到实际存在的文件,赋值给其自己
ifneq ($(dep_files),) #如果不为空,就include这些文件
include $(dep_files)
endif
PHONY += $(subdir-y)
__build : $(subdir-y) built-in.o #__build依赖于子目录 和 当前目录的built-in.o
$(subdir-y): #对当前目录的所有子目录执行以下,这个没有依赖文件,所以是无条件执行的
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs) #本目录下的built-in.o 依赖于 当前目录所有的.o 和 各下级目录生成的 built-in.o
$(LD) -r -o $@ $^ #把当前目录下所有的.o 和 各级目录下所有 built-in.o 编译成一个本目录的 built-in.o
dep_file = .$@.d
%.o : %.c #当前目录的源文件,依赖于.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $< #当前目录下,所有的.o文件依赖于.c,编译生成.o文件,并创建对应的依赖文件
.PHONY : $(PHONY)
总结Makefile.build:
- 执行时,主要生成的是__build这个伪文件,设置这个伪文件的目的就是执行它的各个依赖,而不是真正的要去生成它
- __build : $(subdir-y) built-in.o: 伪文件的依赖有两个,一个是子目录(包括子目录的个数和名字等),另外一个就是当前目录的built-in.o,两个只要其中一个有变动,就会重新执行build。
- $(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
检测 __build时, ( s u b d i r − y ) 是 其 依 赖 文 件 , 而 (subdir-y)是其依赖文件,而 (subdir−y)是其依赖文件,而(subdir-y): 这里是没有依赖文件,无条件执行的,所以每次都会执行这个,只要调用了Makefile.build - built-in.o : $(cur_objs) $(subdir_objs) :本目录的built-in.o依赖于本文件的.o和下级目录的.o
而本目录下的.o文件又依赖于.c(或者之前生成的依赖文件),这样本文件下的.c又会得到编译
先进入每个子目录,编译各自的.c文件(根据.c或者之前生成的依赖文件),再将该子目录下的所有.o编译成build-in.o,再递归向上,每一层目录出来都只有一个build-in.o文件,最终递归到顶层目录,各子目录和顶层的main.o合并得到顶层目录的build-in.o,最后在Makefile(不是Makefile.build)中,将顶层的build-in.o连接成可执行文件。