编译过程
-
流程图
编译过程图
- 预处理 [Preprocessing]
- 将所有的注释以空格代替
- 将所有的
#define
删除,并展开所有的宏定义 - 处理条件编译指令
#if #ifdef #elif #else #endif
- 处理
#include
, 展开文件包含 - 保留编译器需要使用的
#progma
指令
预处理指令示例:gcc -E *.c -o *.i
- 编译优化
- 对预处理生成的文件进行语法分析、词法分析、语义分析
语法分析:分析表达式是否遵循语法规则
词法分析:分析关键字,标识符,立即数是否合法
语义分析:在语法分析基础上进一步分析表达式是否合法 - 分析结束后进行 [代码优化] 生成相应的汇编代码文件
编译指令示例:gcc -S *.i -o *.s
- 汇编
- 汇编过程是用汇编器将汇编代码转变为机器可以执行的指令,也就是机器指令,也称为目标文件(.o)。
- 每条汇编指令几乎都对应一条机器指令
汇编指令示例:gcc - c *.s -o *.o
- 链接
- 链接是指将目标文件最终生成可执行文件。
静态链接(.a、.lib):目标文件直接加入到可执行文件
动态链接(.so、.dll):在程序启动后才动态加载目标文件
6.常用 g++ (gcc) 编译选项
-
-shared
指定生成动态链接库 -
-static
指定生成静态链接库 -
-fPIC
表示编译为位置独立的代码,用于编译共享库。目标文件需要创建成位置无关码, 理念上就是在可执行程序装载它们的时候,它们可以放在可执行程序的内存里的任何地方 -
-L.
表示要连接的库所在的目录。 -
-l
指定链接时需要的动态库。编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a/.so来确定库的名称。 -
-Wall
生成所有警告信息。 -
-ggdb
此选项将尽可能的生成gdb 的可以使用的调试信息。 -
-g
编译器在编译的时候产生调试信息。 -
-c
只激活预处理、编译和汇编,也就是把程序做成目标文件(.o文件) 。 -
-Wl,options
把参数(options)传递给链接器ld 。如果options 中间有逗号,就将options分成多个选项,然后传递给链接程序。 -
-Wl,-rpath=选项
链接器在可执行文件头中记录动态库的路径,动态加载器运行时读取动态库路径,加载动态库。 -
-O0
不进行优化处理。 -
-O 或 -O1
优化生成代码 -
-O2
进一步优化。 -
-O3
比-O2
更进一步优化,包括 inline 函数。
- 分析程序依赖项
-
readelf -d xxx
查看程序依赖的so文件,以及rpaths 路径 -
ldd
指令可以查看该程序依赖的so,查找到的具体路径。看看是否符合预期
- 链接时so、a文件查找顺序
- -L配置的目录
- g++内置的系统目录,例如/usr/lib…
- 系统环境变量(例如LIBRARY_PATH )指定的目录
- 运行时so文件查找顺序
- 应用程序的当前目录
- 可执行文件中储存的rpath(run path)。readelf -d xxx指令可以查看文件的runpath信息。如果该选项指定了依旧失效,说明依赖的so文件还存在更多依赖在其他目录没有明确(常用)
- 环境变量指定的目录(例如LD_LIBRARY_PATH)
Makefile基本语法
- 数据类型
字符串、字符串数组 - 定义变量
var := folder
, 定义变量var, string类型,值为folder - 定义数组
var := hello world folder
,定义变量var,为数组类型,值是["hello", "world", "folder"] - 定义的多种方式
= 赋值var = folder
递归赋值,Makefile全部执行后决定取值(不常用)
:= 赋值var := folder
基本赋值,当前所在位置决定取值(常用)
?= 赋值var ?= folder
如果没有赋值,则赋值为folder
+= 赋值var += folder
添加值,在var后面添加值。可以认为数组后边增加元素
append,var := hello
,var += world
,结果是hello world,中间有空格 -
$(var)
${var}
,在这个位置解释为var的值,例如:var2 := $(var)
-
$(func param)
,调用Make提供的内置函数
例如:var := $(shell echo hello)
,定义var的值为执行shell echo hello
后的输出
$(info $(var))
,直接打印var变量内容
var := $(subst a,x,aabbcc)
,替换aabbcc中的a为x后赋值给var,结果xxbbcc - 逻辑语法:
ifeq
、ifneq
、ifdef
、ifndef
ifeq($(var), depends)
name := hello
endif
- Makefile系统通配符
-
*
匹配0个或者任意个字符 -
?
匹配任意一个字符 -
[]
我们可以指定匹配的字符放在[]
中 -
%
字符作用类似于通配符*
,它和*
的区别是,模式匹配字符可以对目标文件与依赖文件进行匹配。比如说我们在写 makefile 的时候,经常会写这样的一条规则%.o:%.c
这里的%
代表的是一个文件名,也就是一个字符串。首先,所有的.o
文件会组成一个列表,然后挨个被拿出来,%
表示当前拿出来的%.o
文件的文件名,然后根据文件名%
来寻找和.o
文件同名的%.c
文件,并把取出的%.o
文件和寻找到的%.c
文件用于执行后面的命令。这是 makefile 中自动匹配的一种规则。
在变量定义的时候通配符
*
不会自动展开,需要使用wildcard
函数来实现
- Makefile自动变量
-
$@
表示目标文件 -
$^
表示所有的依赖文件 -
$<
表示第一个依赖文件 -
$?
表示比目标还要新的依赖文件列表
- Makefile函数
- 通配符扩展函数
$(wildcard <pattern...>)
功能:它被展开为已经存在的、使用空格分开的、匹配此模式的指定文件夹下的所有文件列表
返回:符合条件的文件列表 - 字符串替换函数
$(subst <from>,<to>,<text>)
功能: 把字符串<text>
中的<from>
替换为<to>
返回: 替换过的字符串
# Makefile 内容
all:
@echo $(subst t,e,maktfilt) <-- 将t替换为e
# bash 中执行 make
$ make
makefile
- 模式字符串替换函数:
$(patsubst <pattern>,<replacement>,<text>)
功能: 查找<text>
中的单词(单词以"空格", "tab", "换行"来分割) 是否符合<pattern>
, 符合的话,用<replacement>
替代
返回: 替换过的字符串
# Makefile 内容
all:
@echo $(patsubst %.c,%.o,programA.c programB.c)
# bash 中执行 make
$ make
programA.o programB.o
- 模式匹配替换
说明:使用匹配符%匹配变量,使用 % 保留变量值中的指定字符串,然后其他部分使用指定字符串代替。
.PHONY: all
SRC := main.c sub.c
OBJ := $(SRC:%.c=%.o)
all:
@echo "SRC = $(SRC)"
@echo "OBJ = $(OBJ)"
# 执行结果
# make
SRC = main.c sub.c
OBJ = main.o sub.o
- 去空格函数
$(strip <string>)
功能: 去掉<string>
字符串中开头和结尾的空字符
返回: 被去掉空格的字符串值
# Makefile 内容
VAL := " aa bb cc "
all:
@echo "去除空格前: " $(VAL)
@echo "去除空格后: " $(strip $(VAL))
# bash 中执行 make
$ make
去除空格前: aa bb cc
去除空格后: aa bb cc
- 查找字符串函数
$(findstring <find>,<in>)
功能: 在字符串 <in> 中查找 <find> 字符串
返回: 如果找到, 返回 <find> 字符串, 否则返回空字符串
# Makefile 内容
VAL := " aa bb cc "
all:
@echo $(findstring aa,$(VAL))
@echo $(findstring ab,$(VAL))
# bash 中执行 make
$ make
aa
- 过滤函数
$(filter <pattern...>,<text>)
功能: 以<pattern>
模式过滤字符串<text>
, 保留 符合模式<pattern>
的单词, 可以有多个模式
返回: 符合模式<pattern>
的字符串
# Makefile 内容
all:
@echo $(filter %.o %.a,program.c program.o program.a)
# bash 中执行 make
$ make
program.o program.a
- foreach 语法
$(foreach <var>,<list>,<text>)
# Makefile 内容
targets := a b c d
objects := $(foreach i,$(targets),$(i).o)
all:
@echo $(targets)
@echo $(objects)
# bash 中执行 make
$ make
a b c d
a.o b.o c.o d.o
- shell 语法
$(shell <shell command>)
它的作用就是执行一个shell命令, 并将shell命令的结果作为函数的返回.
作用和 `<shell command>` 一样, ` 是反引号
通用Makefile
# 检索src目录查找cpp为后缀的文件,用shell指令的find
srcs := $(shell find src -name "*.cpp")
# 将srcs的后缀为.cpp的替换为.o
objs := $(srcs:.cpp=.o)
# 将src/前缀替换为objs前缀,让.o文件方到objs目录下
objs := $(objs:src/%=objs/%)
# 所有objs文件的.o文件改为.mk, 得到mks
mks := $(objs:.o=.mk)
# 1. 增加include_paths选项, 因为头文件需要他们
include_paths := /path1/include \
/path2/include
# 2. 增加ld_liberarys选项,因为链接需要他们
library_paths := /path1/lib \
/path2/lib
ld_librarys := m curl ssl
# 3. 将每一个头肩路径前面增加-I, 库文件路径前面增加-L, 链接选项前面加上-l
# -I 配置头文件路径
# -L 配置库路径
# -lname 配置依赖的so
# 增加run path 变量,语法为
# g++ main.o test.o -o out.bin -Wl,-rpath=/path1/lib /path2/lib
run_paths := $(library_paths:%=-Wl,-rpath=%)
include_paths := $(include_paths:%=-I%)
library_paths := $(library_paths:%=-L%)
ld_librarys := $(ld_librarys:%=-l%)
# 4. 增加compile_flags,增加编译选项,例如我们需要C++11特性等,
# -w避免警告,-g生成调试信息
# -O0优化级别关闭
compile_flags := -std=c++11 -w -g -O0 $(include_paths)
link_flags := $(library_paths) $(ld_librarys)
# 所有的头文件依赖产生的makefile文件,进行include
ifneq ($(MAKECMDGOALS), clean)
include $(mks)
endif
# 定义objs下的o文件,依赖src下对应的cpp文件
# $@ = 左边的生成项
# $< = 右边的依赖项第一个
# 5. 将编译选项增加到g++编译后面
objs/%.o : src/%.cpp
@echo 编译$<,生成$@, 目录是:$(dir $@)
@mkdir -p $(dir $@)
g++ -c $< -o $@ $(compile_flags)
# $^ = 右边的依赖项全部
# 6. 将链接选项增加到g++链接后面
workspace/pro : $(objs)
@echo 这里所有的依赖项是6[$^]
@echo 链接$@
g++ $^ -o $@ $(link_flags)
# 这里由#include $(mks)触发执行
# 不存在.mk文件,这里会执行产生.mk文件
objs/%.mk : src/%.cpp
@echo 生成依赖$@
@mkdir -p $(dir $@)
@g++ -MM $< -MF $@ -MT $(@:.mk=.o) $(compile_flags)
# 定义简洁指令, make pro即可生成程序
pro : workspace/pro
@echo 编译完成
# 定义make run, 编译好pro后执行
run : pro
@cd workspace && ./pro
# 定义make clean, 清理编译留下的垃圾
clean :
@rm -rf workspace/pro objs
debug:
@echo objs is [$(objs)]
.PHONY : pro run debug clean
网友评论