gcc编译程序四个阶段 预处理、编译、汇编、链接

  我们在windows下的时候大部分通过vs等编译器来编写我们的代码,这种编译器有一个统称的名字叫做IDE(Integrated Development Environment)中文名字是集成开发环境,为什么叫它集成开发环境,是因为你只需要安装一个vs你就可以做到对代码的编辑、编译、调试等等,每次我们在vs环境下编写完代码之后拿一下f5程序自动开始执行,我们就能得到程序的执行结果,实际上在你编写完代码之后到程序正确运行并且输出还有很长的路要走。

  我们一般分为四步分别是:预处理、编译、汇编、连接。我们就一步一步的来介绍一些这四步到底都做了些什么内容。

  预处理:预处理阶段的指令一般都是以#来开头的,所以我们编写的程序中像#include、#define等等都是在这一个阶段来完成的,我们经常在编写程序的时候会用到一些头文件stdio也好iostream也好,他们并不是说只是简单的一句话,他们是一些已经被编写好的库我们这里直接引用就可以,所以预处理阶段会把你所有引用的头文件打开来插入到我们本身的程序中。第二个就是预处理阶段会把我们程序中的所有宏进行替换掉,我们经常在程序开头定义一个宏定义,那宏定义的替换就是在预处理阶段完成的。第三个任务是我们在编写程序的时候常常会写入一些注释,这些注释都是给我们程序猿来看的,对程序并没有作用,所以在预处理阶段程序会把我们写入的编译删除掉,机器是看不到我们写的注释的。第四个就是我们的条件编译我们常常会写#ifdef这时候我们不符合条件的那一部分我们机器也是不会看到的,他不会进入到编译阶段。

  讲完这些就要提到我们的gcc,之前我们介绍了vim,vim可以说成是程序的编辑器,linux各个程序是分开的,vim并不能说把你的程序写出来之后还帮你编译帮你运行,这些任务是由gcc来完成的,并且gcc是可以把上边说到的四个步骤一个步骤一个步骤来执行的,我们可以看到每一步程序到底有哪些变化。这里我们就用gcc来看一下每一步程序发生了什么变化。

 这里我们在linux下简单编写一个程序

我们通过命令来让test.c这个文件只走到预处理这一步

这里-E的作用是让程序在预处理完成之后就停止,然后-o是指目标文件,也就是说对test.c进行-E处理之后生成的文件叫什么.i文件是已经经过预处理的C语言源程序。这时候我们ls就能看到我们以前文件夹下边就多了一个.i文件我们打开它。

我们会看到很多我们并不知道的内容,然后我们通过G按键到文件的最末尾。

在八百多行的时候才看到我们自己编写的程序,这时候发现我们的程序中max已经被替换成了5,我们的注释也没有了,而上边那八百多行其实就是我们stdio.h被拆开后的一部分。

  下边我们看第二个阶段编译阶段会做哪些事情。这里我们就要提到我们的汇编语言,我们的C语言也好C++也好是一门高级语言,他是一门人类相对容易理解的一门语言,但是这门语言机器并不能懂,但正是因为机器语言我们人类太难懂才有了这些高级语言,比C语言低级一些比机器语言高级一些就是我们的汇编语言,汇编语言只是在机器语言的基础上利用了一些助记符而已。在第二个阶段主要完成的任务就是由编译器来检查一下你的程序是不是有问题是不是有一些语法错误,我们在vs下编写程序的时候也会编译一下看看我们的程序有没有error或者warning再去执行,但是我们常常为了节省时间会直接让程序运行这不是一个很好的习惯。当你的程序没有问题的时候编译还会把你的程序编程更接近机器语言的汇编语言。

这时候我们将test.i文件经过指令进行编译-S的意思是让我们的文件只进行编译而不进行汇编,生成汇编代码。

打开我们的test.s文件就能看见这一段汇编代码,学过汇编的很容易理解,这里就不过多的讲解。这是第二阶段编译来完成的事情。

 第三阶段是汇编阶段,这一阶段就是把我们第二阶段生成的汇编代码变成我们的可执行文件,也就是把我们的汇编语言变成我们的机器可以执行的机器语言,这是每个程序必须经过的阶段,因为我们的C语言也好汇编也好各种各样的语言机器都是看不懂的。

-C的意思是让我们的程序执行完第三阶段生成一个机器可以看懂的机器语言,这时候我们ls就可以看到生成的.o文件,并且这个文件的颜色和其他颜色都是不一样的。

这时候我们通过vim打开之后会发现这在我们眼里完全就是一堆乱码,但是这才是机器能够读懂的二进制语言。

 这时候是不是我们就可以直接执行了?不是的,虽然现在我们的文件机器是可以看懂了,但是还有很多步骤要执行,比如我们当前的源文件引用了另外的头文件中的函数,或者在源文件中调用了某些库中已经写好的函数,这时候我们执行的时候如果不把他们链接起来都是独立的一块一块的话,程序是不能够正确的执行的,所以我们就要进行第四步连接。

这里直接gcc即可。

这里是几个我们常用的gcc选项。这里我们涉及到了一个新的概念就是函数库的链接,我们在刚刚的程序调用了printf函数,这个函数我们自己并没有实现但是却直接调用了,我们上边也讲到了原因是引用的头文件,但是为什么我们只是简单的写了一个#incldue<stdio.h>就直接把我们的printf函数引用过来了呢?

  原因是这些函数已经被写入到了一个库中,在没有进行特别指定的时候gcc会到某个默认的路径下去寻找这个库,也就是链接到这个库中去把这些文件变成一个整体然后再去执行就没有问题了。函数库一般有两种:静态库和动态库。简单来说静态库就是当我用到这个库的时候,我会直接生硬的把库中的文件添加到你的源文件中,假如我们这个库里边有一百个函数,但是我这里只用了一个printf函数,程序也会把所有的函数都插入到你的程序中,在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。但是动态链接就不一样,动态链接并没有说把库文件的代码插入到可执行文件中,而是在程序执行的时候由链接程序来加载库,这样可以节省大量的系统开销这种库的后缀一般是.so,我们刚刚提到的stdio的名字就是libc.so.6它就是一个动态库。

使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。静态链接的话代码的装载速度快,执行速度也较快,因为编译时它只会把你需要的那部分链接进去,应用程序相对比较大。但是如果多个应用程序使用的话,会被装载多次,浪费内存。这里并没有说确定的界限,所以到底是用动态的还是静态的还是要具体情况具体分析。

猜你喜欢

转载自blog.csdn.net/Hanani_Jia/article/details/81735517