c语言编译过程
预处理–>展开头文件/宏替换/去掉注释/条件编译(main .i)
编译–>检查语法,生成汇编(main.s)
汇编–>汇编代码转换机器码(main.o)
链接–>链接到一起生成可执行程序(a.out)
- 链接过程使用 GNU 的“ld”工具
- 使用“ar”工具维护和管理静态库。
make是如何工作的
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件
- 如果找到,它会找文件中的第一个目标文件,并把这个文件作为最终的目标文件
- 如果目标文件不存在,或是目标文件所依赖的后面的.o文件的文件修改时间要比目标文件新,那么他就会执行后面所定义的命令来生成目标文件
- 如果目标文件所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的,于是make会生成.o文件,然后再用.o文件生成make的最终的目标文件,也就是执行文件
没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行,比如make clean
makefle基本的规则
target : prerequisites
command
prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
make自动推导
只要make看到一个.o文件,它就会自动的把.c文件加在依赖关系中,如果make找到一个whatever.o文件,那么whatever.c就会是whatever.o的依赖文件,并且cc -c whatever.c也会被推导出来
引用其他Makefile
include foo.make
常用语法
添加宏定义
在Makefile中添加宏定义可以用来控制源码的编译,通过CFLAGS中的选项-D定义即可
CFLAGS += -DG_DEBUG
变量赋值与使用
- = 是最基本的赋值
- := 是覆盖之前的值
- ?= 是如果没有被赋值过就赋予等号后面的值
- += 是添加等号后面的值
给变量赋值就表示创建了这个变量,使用变量时需使用 ( ) 或 ()或 ()或{}这样的形式,如果想使用真实的" " 符 号 , 需 要 用 " "符号,需要用" "符号,需要用"$"来表示
后缀名为.d的依赖文件
一个比较大型的工程,我们必需清楚每一个源文件都包含了哪些头文件,并且在加入或删除某些头文件时,也需要一并修改 Makefile,这是一个很没有维护性的工作,为了避免这种繁重而又容易出错的事情,可以使用C/C++编译器的 “-M” 选项,即自动获取源文件中包含的头文件,并生成一个依赖关系。
-M: 显示所有的.h文件
-MM:显示依赖关系
GNU 组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个 name.c 的文件都生成一个 name.d 的 Makefile 文件, .d 文件中就存放对应 .c文件的依赖关系。
%.d: %.cpp
@set -e; rm -f $@; \
$(CXX) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($(notdir $*)\)\.o[ :]*,$*\.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
VPATH += storage/hdCtrl/anr # .c或.cpp文件目录
SOURCES := $(foreach dir,$(VPATH),$(wildcard $(dir)/*)) # 展开VPATH目录的所有文件
C_SRCS = $(filter %.c,$(SOURCES)) # 过滤得到所有.c文件
CPP_SRCS = $(filter %.cpp,$(SOURCES)) # 过滤得到所有.cpp文件
SRCS = $(C_SRCS) $(CPP_SRCS)
C_OBJS = $(C_SRCS:%.c=%.o) # 所有.c文件用.o文件替换
CPP_OBJS = $(CPP_SRCS:%.cpp=%.o) # 所有.cpp文件用.o文件替换
OBJS = $(C_OBJS) $(CPP_OBJS)
DEPS = $(OBJS:.o=.d) # 所有.o文件得到依赖文件.d
include $(DEPS) # 使用include指令将自动生成的依赖关系文件包含进来
set -e的作用
告诉BASHShell当生成依赖文件的过程中出现任何错误时,就直接退出,不然还会继续执行下去;这里有个需要注意的地方:对于规则中的每个命令,make都是在一个新的Shell上运行它的,如果希望多个命令在同一个Shell中运行,则需要用‘;’将这些命令连起来(shell命令在makefile调用时候每行shell都是一个单独的进程,上一行定义的变量在下一行是无效的)
通配符
- 波浪号~字符在文件名中表示当前用户的 H O M E 目 录 , 比 如 / t e s t 表 示 当 前 用 户 的 HOME目录,比如~/test表示当前用户的 HOME目录,比如 /test表示当前用户的HOME目录下的test目录
- *.c 表示所有后缀为c的文件
objects = *.o # 通配符用于变量中不会展开
objects := $(wildcard *.o) # 要让通配符在变量中展开,让objects的值是所有.o的文件名的集合(列出一确定文件夹中的所有.c文件)
$(patsubst %.c,%.o,$(wildcard *.c)) # 展开当前目录文件,把.c替换成.o
打印输出
- 在makefile中打印输出信息的方法是: ( w a r n i n g x x x x x ) 或 者 (warning xxxxx)或者 (warningxxxxx)或者(error xxxxx)
- @echo “xxxxx” (用@字符在命令行前,那么,这个命令将不被 make 显示出来)
反斜杠\
- 书写时,可以将一个较长行使用反斜线\来分解为多行,这样可以使我们的Makefile书写清晰、容易阅读理解。但需要注意:反斜线之后不能有空格(这也是大家最容易犯的错误,错误比较隐蔽)。
[Tab]字符
如果一行以[Tab]字符开始make程序将此行作为一个命令行来处理,make 程序都会将其交给系统 shell 程序去解释执行。
参数
-
-L–>指定连接库的搜索路径 LDFLAGS
-
-l–>指定连接时期望连接的库的名字 LDFLAGS
-
-I–>指定寻找的头文件路径 CPPFLAGS
-
-D–>指定宏定义 CFLAGS CPPFLAGS
-
$@ 表示目标文件
-
$^ 表示所有的依赖文件,若有重复只保留一份
-
$+ 表示所有的依赖文件,不去除重复的
-
$< 表示第一个依赖文件
-
$? 表示比目标还要新的依赖文件列表,以空格分隔
-
-Wall 选项可以打印出编译时所有的错误或者警告信息
-
-g 选项是指可以用gdb调试
-
-O 选项是指优化
-
-fsigned-char 选项是对char类型进行设置(在PC上,char类型默认为signed-char,但是在一些嵌入式设备上,比如armi平台,char类型是当作unsigned char处理的,为了保持与PC一致,可以通过指定CFLAG += fsigned-char进行配置)
自带变量
CFLAGS := -Wall -O2 -g #编译器参数
CFLAGS += -I $(shell pwd)/include #指定编译器头文件(根据实际项目手动修改)
LDFLAGS := -lm -lfreetype -lvga #指定编译器链接库(根据实际项目手动修改)
"order-only”依赖
有时,需要定义一个这样的规则,在更新目标( 目标文件已经存在)时只需要根据依赖文件中的部分来决定目标是否需要被重建,而不是在依赖文件的任何一个被修改后都重建目标。
为了实现这一目的,相应的就需要对规则的依赖进行分类,一类是在这些依赖文件被更新后,需要更新规则的目标;另一类是更新这些依赖的,可不需要更新规则的目标。我们把第二类称为:“ order-only”依赖。书写规则时,“ order-only”依赖使用管道符号“ |”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“ |”左边的是常规依赖,管道符号右边的就是“ order-only”依赖。
LIBS = libtest.a
foo : foo.c | $(LIBS)
$(CC) $(CFLAGS) $< -o $@ $(LIBS)
make在执行这个规则时,如果目标文件“ foo”已经存在。当“ foo.c”被修改以后,目标“ foo”将会被重建,但是当“ libtest.a”被修改以后。将不执行规则的命令来重建目标“ foo”。
就是说,规则中依赖文件$(LIBS)只有在目标文件不存在的情况下,才会参与规则的执行。当目标文件存在时此依赖不会参与规则的执行过程。
变量VPATH
GNU make 可以识别一个特殊变量“ VPATH”。通过变量“ VPATH”可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在时, make 会在此变量所指定的目录下去寻找这些依赖文件。
其实“ VPATH”变量所指定的是 Makefile 中所有文件的搜索路径,包括了规则的依赖文件和目标文件。
定义变量“ VPATH”时,使用空格或者冒号:将多个需要搜索的目录分开。
VPATH = src:../headers # 定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索(当前目录永远是最高优先搜索的地方)
关键字vpath
它所实现的功能和上一小节提到的“ VPATH”变量很类似,但是它更为灵活。它可以为不同类型的文件(由文件名区分)指定不同的搜索目录。
1、vpath PATTERN DIRECTORIES
为所有符合模式“ PATTERN”的文件指定搜索目录“ DIRECTORIES”。多个目录使用空格或者冒号:分开。类似“VPATH”变量。
2、vpath PATTERN
清除之前为符合模式“ PATTERN”的文件设置的搜索路径。
3、vpath
清除所有已被设置的文件搜索路径。
vpath %.h ../headers # 要求make在“../headers”目录下搜索所有以.h结尾的文件(如果某文件在当前目录没有找到的话)
静态库
ar命令用于更新,维护管理静态库。
ranlib命令用于 更新库的符号索引表。
当只执行了ar命令(用于更新)时,ld连接时会仍然报错,查找不到更新的变量或函数,此时需要用ranlib来更新库的符号索引表才行。
libptzlib8k.a: ptzLib8k.o
$(AR) rcs $@ ptzLib8k.o # 由.o文件创建静态库
gcc -o main main.c -L. -lptzlib8k # 在程序中使用静态库
判断
ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
endif
ifdef do_sort
func := sort
else
func := strip
endif
返回值
如果一个规则中的某个命令出错了(命令退出码非零),那么 make 就会终止执行当前规则,这将有可能终止所有规则的执行。
为了做到这一点,忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号 - (在 Tab 键之后),标记为不管命令出不出错都认为是成功的。
嵌套执行 make
subsystem:
$(MAKE) -C subdir 先进入“subdir”目录,然后执行 make 命令
层次结构
support_xxx = y # 配置功能选择
G_DIR_PRJ = $${PWD%APPS*}
G_DIR_PACK = ${G_DIR_PRJ}/package_new/deviceDir/ # 配置目录
GLOBAL_LIB ?= ./lib/lib_hi3520_sdi # 配置库目录
GLOBAL_FLAG += -DSUPPORT_TAG # 配置宏定义
----- 上面可以单独出来作为导入的Rule.make使用 -----
#!/bin/bash
include Rules.make
ifeq ($(GLOBAL_LIB), ERROR) # 路径配置检查
$(error make error! GLOBAL_LIB not defined,"Rules.make" must be wrong!!!)
VPATH = $(GLOBAL_LIB) # 库目录
VPATH += cjson # c/c++文件目录(需要编译的文件目录)
CPPFLAGS += -Icjson # 头文件目录
LDFLAGS += -lsqlite3 # 指定编译器链接库
SOURCES := $(foreach dir,$(VPATH),$(wildcard $(dir)/*)) # 展开VPATH目录的所有文件
C_SRCS = $(filter %.c,$(SOURCES)) # 过滤得到所有.c文件
CPP_SRCS = $(filter %.cpp,$(SOURCES)) # 过滤得到所有.cpp文件
SRCS = $(C_SRCS) $(CPP_SRCS)
C_OBJS = $(C_SRCS:%.c=%.o) # 所有.c文件用.o文件替换
CPP_OBJS = $(CPP_SRCS:%.cpp=%.o) # 所有.cpp文件用.o文件替换
OBJS = $(C_OBJS) $(CPP_OBJS)
DEPS = $(OBJS:.o=.d) # 所有.o文件得到依赖文件.d
PREFIX = $(GLOBAL_PREFIX) # 交叉编译
CC = $(PREFIX)gcc # CC --> arm-hisiv100nptl-linux-gcc
CXX = $(PREFIX)g++
STRIP = $(PREFIX)strip
CFLAGS = $(GLOBAL_FLAG)
CXXFLAGS = $(CFLAGS)
LDFLAGS += -L$(GLOBAL_LIB) # 链接编译出来的库
%.d: %.c
@set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($(notdir $*)\)\.o[ :]*,$*\.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
%.d: %.cpp
@set -e; rm -f $@; \
$(CXX) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($(notdir $*)\)\.o[ :]*,$*\.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
all: $(TARGET_3535) $(TARGET_3531) # 最终目标
$(TARGET_3531): $(OBJS)
$(CC) $(OBJS) $(LDFLAGS) -o $@
$(STRIP) $@
$(STRIP) -x -R .note -R .comment $@
cp $@ $(G_DIR_PACK)/hicore -f
rm $@ -f
$(TARGET_3535): $(OBJS)
$(CC) $(OBJS) $(LDFLAGS) -o $@
$(STRIP) $@ # 编译strip减小程序大小
$(STRIP) -x -R .note -R .comment $@
cp $@ $(G_DIR_PACK)/hicore -f
rm $@ -f
include $(DEPS) # 使用include指令将自动生成的依赖关系文件包含进来
.PHONY: all hi3535 hi3531 clean prj
hi3535: $(TARGET_3535)
@echo "make 3535"
hi3531: $(TARGET_3531)
@echo "make 3531"
clean:
-rm -f $(TARGET) $(OBJS) $(DEPS)
make -C ./cjson/ clean
prj:
make -C ./cjson/ -j16
make -j16
参考
- 跟我一起写Makefile
- 随笔分类 - makefile
- Makefile V3.8手册[ 提取码: 4ivo]