文章目录
- GCC
- gcc的安装
- gcc的工作流程
- makefile
- makefile的规则
- 工作原理
- 自动生成
- makefile的变量
- 自定义变量
- 预定义变量
- 自动变量
- 模式匹配
- 函数
- wildcard函数
- patsubst函数
- 伪声明
GCC
gcc全程是(GNU compiler collection CNU编译器套件),是由GNU开发的编程语言编辑器,它包含有gcc、g++,但是不仅限于这两种编译器;gcc不仅可以编译c/c++,还可以编译其他的语言包括java等,gcc可以实现跨不同的硬件平台编译,也就是交叉编译,比如在a平台可以编译b平台上的程序,常用支持linux、windows等。
gcc的安装
# 安装必须有管理员程序
# ubuntu
$ sudo apt update # 更新本地下载列表,获取最新的下载地址
$ sudo apt install gcc g++
# centos
$ sudo yum update
$ sudo yum install gcc g++
# 查看gcc版本
$ gcc -v
$ gcc --version
$ g++ -v
gcc的工作流程
gcc编译器对程序的编译主要分为4个阶段:预编译(预处理)、编译和优化、汇编、链接,gcc的编译器可以将这4个步骤合并成一个,下面分别介绍这四个步骤做了什么事情
- 预编译(预处理):在这个阶段主要做了三件事情:展开头文件、宏替换、去掉注释行,这一阶段由gcc的预处理器完成,最终得到的还是源文件,文本格式。
- 编译:这个阶段需要调用gcc编译器对文件进行编译,最终得到一个汇编文件
- 汇编:这个阶段需要调用gcc的汇编器对文件进行汇编,最终得到一个二进制文件
- 链接:这个阶段需要调用gcc的链接器对程序需要调用的库进行链接,得到一个可以执行的文件
分步完成:比如有一个为file.c的源文件(麻烦版)
一步到位:使用
gcc file.c -o aa
可以直接生成编译链接后的file的可执行文件aa,直接使用./aa
就可以执行。
GCC用到的参数
-E
:对文件进行预处理,不进行编译,生成的还是源文件-S
:对文件进行编译,生成一个汇编文件-c
:对文件进行汇编,生成一个二进制文件-o
:用来指定生成的文件的名称-I
:指定include包含文件的搜索目录
makefile
- 一般来说,使用gcc的命令行进行程序的编译在单个文件下是非常方便的,但是在实际的应用中,一个工程总不会是由单个文件构成的,往往是由多个文件构成的,当工程的文件逐渐增多,甚至非常庞大,使用GCC进行编译就会变得力不从心,这个时候可以使用make构造工具来完成这个艰巨的任务。
- make是一个解释makefile中指令的命令工具。make工具在构造项目的时候需要加载一个makefile的文件,makefile关系到整个工程的编译规则,当一个工程中的源文件不计其数,按照类型、功能、模块划分在若干目录中时,这些文件之间存在着各种的依赖关系,makefile可以定义一系列的规则来指定哪一些文件需要先编译,哪一些文件需要后编译,哪一些文件需要重新编译等。
- mekefile类似于一个shell脚本,所以也可以执行操作系统的一些命令。
- 使用makefile可以自动化编译,极大的提高了软件开发的效率
- makefile文件有两种命名的方式
makefile
和Makefile
,构建的时候在哪一个目录下执行make命令,哪一个目录的makefile文件就会被加载,因此一个项目中可以有多个makefile文件,分别存放在不同的项目目录中。
# 比如当前目录下有以下文件:add.c,clean,dic.c,head.h,main.c,makefile,sub.c
# 如果是使用gcc来对文件进行编译可以执行一下命令,生成app可执行命令
$ gcc *.c -o app
# 如果使用make命令可以直接
$ make
# 如果编译完成,可以直接使用以下命令删掉所有的编译文件
$ make clean
makefile的规则
# 每条规则的语法格式为:
target1,target2,... : depend1.depend2,...
command
......
......
每一条规则都有三个部分组成:目标(target)、依赖(depend)、命令(command)
-
命令(command)
:当前这条规则的动作,一般情况下这个动作都是一个shell命令;动作可以是一个也可以是多个,每个命令前都必须有一个Tab缩进并且独占一行 -
依赖(depend)
:规则所必须的依赖条件,如果规则不需要任何依赖,那么依赖可以为空;当前的规则可以是其规则的某一个目标,这就形成了规则之间的嵌套;依赖可以有多个 -
目标(target)
:规则中的目标,目标与命令是对应的;规则中可以有多个命令,多条命令可以生成多个目标,所以目标可以有多个;通过规则中的命令值执行一个动作,不生成任何文件,这样的目标叫做伪目标
举一个不太恰当的比喻,目标、依赖、命令的关系就好像是用材料做蛋糕的关系:目标就是我们要做的蛋糕,依赖就是做蛋糕原材料,命令就是用原材料做蛋糕的具体步骤,一个语法正确的规则,往往是一条shell命令对依赖关系的处理得到目标的过程。
# 规则中的嵌套关系
# 规则1
app:a.o b.o c.o
gcc a.o b.o c.o -o app
# 规则2
a.o:a.c
gcc -c a.c -o a.o
# 规则3
b.o:b.c
gcc -c b.c -o b.o
# 规则4
c.0:c.c
gcc -c c.c -o c.o
工作原理
- 在调用make命令编译程序的时候,make会首先找到makefile文件的第1个规则,分析并执行相关的动作。但是需要注意的是,很多时候要执行的动作(命令)中的以来关系是不存在的,如果使用的以来关系不存在这个动作也不会被执行。
- 遇到以上的情况,首先会将需要的依赖关系生成出来,以此类推,直到makefile的第一条规则的所有的依赖关系都被生成,第一条命令就可以基于这些依赖生成对应的目标,make任务也就结束了。
- 换句话说,makefile的后续的规则,都是为了第一条命令来服务的。
- 如果想要执行单条规则,可以使用
make 目标名
来执行单条规则。
情况1:依赖存在,但是目标不存在
- 直接根据依赖生成目标
情况2:目标和依赖都不存在,目标时间戳>依赖时间戳,属于正常情况,不执行
情况3:依赖的时间戳>目标的时间戳,make重新生成目标
自动生成
make有自动推导能力,不会完全依赖与makefile。换句话说,make有一些隐藏的默认规则,这些规则就算不编写在makefile,make也会自动执行对应的规则,生产目标文件。
有的时候,我们可以不使用默认的规则生成目标,比如在编译一个多文件的程序中,如果只使用一条规则对多文件进行编译,当我们修改其中的一个文件时,通过make构建工具还是会把整个的文件全部编译一次,如果文件的数量很多,就会非常消耗时间;但是如果我们把默认规则都列举出来,就可以进行分步编译,当只修改其中一两个文件是,不需要对全部的依赖文件都编译一次,可以大大的提高编译的效率。
如果文件有修改,只编译修改的东西,可以加快编译的速度,只编译对应修改的以来关系的链条
makefile的变量
makefile的变量分为三种:自定义变量、预定义变量和自动变量
自定义变量
用户自己定义的变量叫做用户自定义变量,makefile的变量是没有类型的,直接创建变量的名字然后给其赋值即可
# 正确的定义自定义变量
变量名=变量值
在给makefile的变量赋值之后,可以使用$(变量的名字)
来将变量的值取出来
# 用户自定义变量
# 定义一个变量
obj=a.o b.o c.o
# 规则1
app:$(obj)
gcc $(obj) -o app
预定义变量
makefile中有一些已经定义的变量,用户可以直接使用这些变量,不用进行定义,在某一些条件下,makefile会使用这些预定义的值进行编译,这些预定义变量的名字一般都是大写,经常采用的预定义变量如下表所示:
CC
:默认值为 cc ,是gcc的意思,一般用来编译c程序CXX
:默认值为 g++,一般用来编译c++程序CFLAGS
:c语言编译器的编译选项,没有默认值,要用户自己赋值
# 预定义变量
# 定义变量
target=app
obj=a.o b.o c.o
CFLAGS=-O3 # 代码优化(有四种优化级别分别是0-3)
# 规则1
$(target):$(obj)
$(CC) $(obj) -o $(target) $(CFLAGS)
自动变量
自动变量用来代表这些规则中的目标文件和依赖关系,并且它们只能在规则的命令中使用
$@
:表示目标文件的名称。包括扩展名$^
:在依赖项中,所有不重复的依赖文件,这些文件直接以空格分开$<
:在依赖项中的第一个依赖文件的名称
# 自动变量
# 定义变量
target=app
obj=a.o b.o c.o
# 规则1
$(target):$(obj)
gcc $^ -o $@
模式匹配
通过一个公式来代表若干满足条件的规则,用于精简makefile
以下代码中可以使用通配符%
来匹配名字
# 规则中的嵌套关系
# 规则1
app:a.o b.o c.o
gcc a.o b.o c.o -o app
# 规则2
a.o:a.c
gcc -c a.c -o a.o
# 规则3
b.o:b.c
gcc -c b.c -o b.o
# 规则4
c.0:c.c
gcc -c c.c -o c.o
# 规则中的嵌套关系
# 规则1
app:a.o b.o c.o
gcc a.o b.o c.o -o app
%.o:%.c
gcc -c $< -o $@
函数
makefile中有很多函数并且所有的函数都具有返回值,makefile中的函数写法如下:
$(函数名 参数1,参数2,参数3,...)
wildcard函数
这个函数的主要作用是获取指定目录下指定类型的文件名,其返回值是以空格分割的,指定目录下所有符合条件的文件名列表
# 函数原型
$(wildcard PATTERN...)
参数:指定某个目录,搜索这个路径下指定类型的文件,可以指定多个目录,每一个路径之间用空格隔开
# 返回值:得到若干文件的文件列表,文件名之间使用空格间隔
# 搜索三个不同目录下的.c源格式文件
src=$(wildcard *.c ./sub/*.c /home/robin/b/*.c)
patsubst函数
这个函数的功能是按照孩子定的模式替换指定的文件名的后缀
# 有三个参数,参数之间用逗号隔开
$(patsubst pattern,replacement.text)
# pattern:模式字符串。需要指出要被替换的文件名的后缀是什么,文件名和路径不需要关心,使用 % 即可,如 %.c
# replacement:模式字符串,致命pattern中的后缀最终要被替换为什么;还是使用 % 表示路径和名字,指定新的后缀名即可,比如 %.o
# text:该参数中存储要被替换的原始数据
# 返回值:返回被替换过后的字符串
# 把变量src中所有文件名的后缀从.c替换为.o
src=a.c b.c c.c
obj=$(patsubst %.c,%.o,$(src))
使用函数搜索当前的目录下的c文件,并且修改名字
# 规则中的嵌套关系
# 规则1
app:a.o b.o c.o
gcc a.o b.o c.o -o app
%.o:%.c
gcc -c $< -o $@
target=app
src=$(whilecard *.c)# 搜索当前目录下所有的.c文件
obj=$(patsubst %.c,%.o,$(src))
$(target):$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $<
伪声明
target=app
src=$(whilecard *.c)# 搜索当前目录下所有的.c文件
obj=$(patsubst %.c,%.o,$(src))
$(target):$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $<
# 这是一条伪声明,没有依赖,也不生成文件(伪目标)
clean:
rm $(obj) $(target)
对伪目标的声明,以避免make对文件的时间戳进行检测,需要使用
.PHONY
关键字,声明方式为.PHONY:伪文件名称
# 最终版
target=app
src=$(whilecard *.c)# 搜索当前目录下所有的.c文件
obj=$(patsubst %.c,%.o,$(src))
$(target):$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $<
# 这是一条伪声明,没有依赖,也不生成文件(伪目标)
.PHONY:clean
clean:
-rm $(obj) $(target) # 加-表示即使第一条命令执行失败,后面的命令也要继续执行
...