第五章 linux编译器——gcc/g++的使用
- 一、编辑器与编译器的区别
- 二、gcc/g++的编译过程
- 前言
- 1、阶段1:预处理(头文件、宏的替换)
- (1)作用
- (2)指令
- (3)示例
- 2、阶段2:编译(生成汇编代码)
- (1)作用
- (2)指令
- (3)示例
- 3、阶段3:汇编(生成二进制文件)
- (1)作用
- (2)指令
- (3)示例
- 4、阶段4:链接(生成可执行文件)
- (1)作用
- (2)静态链接与动态链接
- (3)指令
- (4)示例
- 5、指令的简化
- 三、Linux项目自动化构建工具——make/Makefile
- 1、背景
- 2、Makefile文件的编写
- (1)项目的构建
- 依赖关系
- 依赖方法
- 顺序说明
- (2)项目的清理
- 3、make相关的指令
- (1)项目的自动化搭建——make
- (2)项目的自动化清理——make clean
一、编辑器与编译器的区别
在前一个章节中提到的vim是代码编辑器,代码编辑器的功能是让我们输入代码的。所以从这个角度出发,我们常见的记事本也可以算作一个代码编辑器,只不过这个编辑器没有高亮等功能。而我们今天介绍的代码编译器的功能是把编辑器中写入的代码翻译为机器能够听懂的语言并生成可执行文件。所以这二者是有本质区别的。
二、gcc/g++的编译过程
前言
gcc是用来编译C语言代码的,g++是用来编译C++代码的。这两个编译器在指令和编译过程等方面几乎是一样的。所以作者在这里只以g++为例子介绍编译器。
1、阶段1:预处理(头文件、宏的替换)
(1)作用
预处理过程主要作用是将代码中的宏定义进行替换,同时将头文件写入到我们代码的前面,除此以外还会去掉我们写的注释。
(2)指令
g++ -E A.cpp -o A.i
g++是指我们想要用的编译器是g++,"-E"的作用是让g++在预处理结束后停止编译过程,“A.cpp”是需要被处理的c++代码。“-o”是指利用前面处理过的内容生成一个目标文件,“A.i”是我们目标文件的名字,其中“.i”是与处理文件的后缀。
(3)示例
下面是作者写的源文件:
接下来,我们进行文件的预处理工作,在命令行中输入刚刚讲解的指令,如下图所示:
根据上面这个图我们会发现,预处理过后的文件非常的大,而这背后的主要原因就是在预处理的过程中,编译器将我们头文件引入到了代码中。
然后我们打开预处理后的文件:“t1.i”,结果如下图所示。
通过观察上面的图,我们会发现,除了头文件被替换了以外,我们的宏定义也被替换了。注释也被删除了。
2、阶段2:编译(生成汇编代码)
(1)作用
以g++为例,在编译阶段,编译器会将预处理过后的C++文件翻译为汇编语言的文件。
(2)指令
g++ -S A.i -o A.s
(3)示例
如上图所示,编译工作结束后,我们的目录下多了一个.s的文件。那么我们可以预览一下这个文件里的内容。
我们发现,我们原本用C++写的代码已经被翻译为了汇编代码。
3、阶段3:汇编(生成二进制文件)
(1)作用
在汇编阶段,编译器会将编译后的汇编文件翻译为二进制文件。
(2)指令
g++ -c A.s -o A.o
(3)示例
我们可以预览一下汇编过后的代码。
由于我们vim无法查看用二进制写的代码,所以这里显示了乱码。
4、阶段4:链接(生成可执行文件)
(1)作用
在预处理阶段,编译器会将头文件的内容拷贝到我们写的代码文件中。这里就会有一个问题,头文件中只写了函数的声明,也就是说头文件的作用仅仅是告诉编译器这个函数的的确确存在,但是并没有告诉编译器这个函数到底是怎么实现的,函数的具体实现是写在库文件中的。我们此处介绍的链接其实就是将我们的代码和这些写有函数实现的库文件之间构建联系,从而生成一个可执行文件。
(2)静态链接与动态链接
写有函数实现的库文件分为两大类,一类是静态库,一类是动态库。
如果是静态链接的话,编译器就会将静态库中的代码实现拷贝到我们的代码中。这种静态链接的方式有好处也有坏处。好处在于,如果我们的静态库丢失了,由于我们已经对函数实现进行了拷贝,所以代码文件是独立的,依旧可以正常运行。但坏处也显而易见,由于该过程进行了大量的拷贝工作,所以会浪费很多空间。
动态链接则与之相反,动态链接并不会将代码实现进行拷贝,而是和动态库建立一种联系,从而让编译器了解到函数的具体实现。由于很多代码文件都和一个动态库建立联系,所以动态库又叫做共享库。那么这么做有什么优点呢?首先,由于动态链接并不会将代码拷贝到文件中,所以动态链接的方式能够节约大量的空间。缺点也显而易见,如果动态库发生了丢失,那么该文件将无法执行。
(3)指令
g++ A.o -o A
(4)示例
我们只要访问生成的文件,即可看到我们的代码运行结果。
5、指令的简化
如果每次运行代码都需要输入上面四个指令的话,其实是非常麻烦的。所以我们可以将上面四个编译命令合并成一个简化的命令。
g++ A.cpp -o A
三、Linux项目自动化构建工具——make/Makefile
1、背景
当一个工程越来越大的时候,源文件就会越来越多,我们的编译命令就会越来越麻烦。无论是源文件的数量,还是文件先后的编译顺序都会对我们的编译工作造成困扰。那么为了解决这个问题,我们就需要了解一下自动化构建的工具:make。这里先解释一下标题,make是一个命令,Makefile是一个文件,Makefile文件中要写的是编译的指令。当写好这个文件后,我们只需要用make指令调用Makefile文件即可。
2、Makefile文件的编写
(1)项目的构建
为了后续的讲解更加清楚,作者在这里直接给出项目构建的Makefile文件。
依赖关系
在上面的图片中,我们将每两行看成一组,在每一组的第一行就是依赖关系。冒号前面的绿色字体所描绘的文件的生成依赖于冒号后面的文件。
依赖方法
在每一组中,第二行紫色的字体所写的代码是依赖方法,即如何用被依赖的文件生成目标文件。
上图只是为了让大家更好的理解Makefile文件的书写,其实我们可以直接放一个简化后的命令在上面,不用细化这么多。即下图:
顺序说明
从上面的图片中我们还发现了一个规律,我们的顺序是倒着来的,从可执行文件倒推到,cpp文件。这里用到的其实是栈这种数据结构。如果无法用栈理解的话,读者可以强行记住这里是从后往前写的。我们这利用一下后面马上要介绍的make指令运行一下Makefile文件。将会发生下面的现象。
通过这张图片,我们会发现,虽然我们是倒着写的,但是最终是正着执行的。即我们先写的是后执行的,即后进先出,恰好是栈这种数据结构的特点。
(2)项目的清理
我们还是先给出项目清理的makefile文件写法。如下图所示:
我们先看最后两行,这两行很好解释。clean指的是清楚命令。第二行指的是我们清楚目标文件的删除命令。而倒数第三行则不一样。.PHONY
是强制执行的命令,冒号后面的clean是指我们要强制执行哪一条命令。那么这一行的意思就是,我们要强制执行clean命令。
3、make相关的指令
(1)项目的自动化搭建——make
当我们写好Makefile文件后,我们只需要在命令行中输入一个make即可。如下图所示:
这里还要介绍一下,make命令的重复使用问题。
我们的如果我们的代码源文件没有更改的话,我们的make命令只能执行一次。如果重复执行的话,会出现下面的提示:
那么make怎么知道我们的源代码如何更改呢?其实很简单,Linux会记录文件的修改时间,make会通过时间的比较来判断你是否修改过。但是如果我们想强制运行呢?其实也是可以的,我们刚刚介绍了一个强制执行的命令:.PHONY
。这里就可以使用以下,如下图:
我们想要重复执行哪一个命令,就在前面加上强制执行的命令,冒号后面则是我们的目标文件。结果如下图所示:
(2)项目的自动化清理——make clean
如果想要清理我们的项目,我们只需要在命令行中输入make clean即可。