目录
前言
1.背景知识介绍
2.预处理阶段
3.编译阶段
4.汇编阶段
5.链接过程
5.1 函数库
5.1.1 静态库和动态库
5.2 链接过程的具体内容
总结
前言
大家好呀,许久没给大家更新了,对于我们在Linux环境下编程,之前小编只是给大家介绍了有关编辑器vim的相关操作,但是对于我们编辑后关键是生成对应的可执行程序,那么产生可执行程序就一定需要使用我们相关的编译器,那么今天给大家带来的就是我们Linux环境下编译器的使用。
1.背景知识介绍
首先我们需要知道的就是我们的文件到可执行程序一共需要进行预编译,编译,汇编,链接,四个过程在这个过程中我们的编译器执行的工作是各不相同的,而我们编译器使用的相关指令也是不同的,那么小编就以这个过程逐一给大家介绍我们Linux下编译器的使用方式,这里我们以gcc为例,g++和我们的gcc使用方式是一致的,但是我们的g++针对的是我们c++语言的编译,但同时也能编译我们的C语言程序,而我们的gcc就只是针对我们的C语言程序。
2.预处理阶段
我们的预处理阶段执行的工作主要有宏定义替换,头文件展开,条件编译,去注释等,而这里小编就不给大家详细介绍了,对于该过程小编会在另一篇文章中给大家进行详细的说明,讲解,这里就仅仅给大家简单演示一下即可,但在演示之前有一行相关的命令需要大家了解一下
预处理指令是以#号开头的代码行。
实例: gcc –E hello.c –o hello.i
选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程。
选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序
首先我们需要在Linux环境下创建一个文件,然后就是用我们的文件编译相关的代码,如下:
这里我们可以看到我们的程序中有着对应的有头文件,宏定义,以及注释等,对于条件编译,小编会在另一篇文章中给大家具体介绍。
那么这里我们输入指令:gcc –E text.c –o text.i,产生我们对应的text.i文件
这里我们用vim打开我们的text.i文件看看该具体内容:
这里我们可以看到在text.i文件中我们的头文件被展开了,实际上我们的头文件是一个800多行的大文本,这里我仅仅给大家截取了部分,这里我们很轻松的观察到我们的注释被删除了,以及我们的宏被替换了。那么预处理后我们接下来进行的就是我们的编译阶段。
3.编译阶段
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言,那么我们如何观察到这个现象呢?这里我们就需要给大家介绍一个gcc相关指令去给大家演示这个过程。
我们可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码(编译工作做完就停下来)。
实例: gcc –S hello.i –o hello.s
这里给大家简单演示一下:
这里我们产生了text.s文件,那么我们打开开一下该内容:
这里我们可以看到这里显示了汇编代码。
4.汇编阶段
我们的汇编阶段就是把我们的将我们的汇编代码转化为我们的二进制目标文件,对于编译和汇编两个过程,这里就是简单的给大家一笔带过一下,具体的过程小编会在后续给大家介绍。那么我们如何查看这个结果呢?废话不多说,直接上例子:
这里我们在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了(汇编工作做完就停下来)
实例: gcc –c hello.s –o hello.o
.o文件就是我们的目标文件,但是该不可以独立执行,虽然该已经是二进制文件了,还得经过链接才可以
那么我们看看具体操作:
这里我们打开看一下:
这里我们看到出现了一堆乱码,这是由于我们这里是二进制文件导致的。
5.链接过程
链接过程实际上就是将可重定位目标二进制文件和库进行链接形成可执行程序,相比于之前的目标文件,我们的链接过程形成的可执行程序就是最后我们最后形成的可独立执行的文件
那么我们这里如何将我们的二进制文件转化为可执行文件呢?这里我们还需要使用到一个指令:
实例: gcc hello.o –o hello
这里我们生成我们的可执行程序,那么我们需要如何去运行这个可执行程序呢?这里我们的方式是这样的
./text
那么我们这里的原理是什么呢?实际上,linux要运行某个程序就一定要找到这个程序的二进制文件,所以我们通常使用 ./文件名 去运行文件指的就是在当前路径找到我们的二进制文件,但是对于指令的执行我们的操作系统是会到特定的路径去寻找的。
5.1 函数库
首先我们需要了解一个小概念就是,我们的程序为什么能够在我们的操作系统中运行呢?
实际上:我们需要的开发环境除了vs,gcc,g++,更重要的是语言本身的头文件和库(也就是我们的系统中一定要提前或者后续安装上对应的头文件库文件)
我们在安装vs2019时也需要选择对应的开发包,也就是将对于的库或者头文件进行了下载安装
我们Linux关于C语言的头文件放置于我们的/usr/include/目录下
这就说明我们的库对于我们一个程序的运行是极其重要的,那么我们的库究竟在我们的程序运行中充当一个什么角色呢?
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
实际上对于我们的编译型语言,安装开发包,必定是下载安装对应的头文件+库文件,而方法的实现就是在库当中(库其实就是把源文件,经过一定的翻译然后打包———只给你提供一个源文件即可,不用给你提供太多的源文件,也可以达到隐藏源文件的目的)而我们最后的软件也就是我们头文件提供方法的声明+库提供方法的实现+我们的代码。
5.1.1 静态库和动态库
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。 gcc hello.o –o hello
那么我们如何知道我们的可执行文件使用的是静态库还是动态库呢?这里我们可以使用file指令去查看:
这里我们可以看到我们这里进行的是动态链接方式这也是我们gcc默认的链接方式
同时我们也可以使用ldd指令可以查看一个可执行程序所依赖的动态库,也就是
对应着不同的操作系统和软件我们的动态库和静态库的文件后缀是不同的,他们分别对应的如下:
Linux:.so(动态库),.a(静态库)
windows: .dll(动态库),.lib(静态库)
还有一点就是在我们的机器上只会默认安装动态库,不会安装静态库,而如下就是我们安装静态库的指令:
C语言:yum install -y glibc-static
C++: yum install -y libstdc++-static
5.2 链接过程的具体内容
既然我们存在着我们的动态库以及我们的静态库,那就说明我们所对应得也就存在着我们的动态链接和静态链接两种方式:
1.动态链接:当一个程序在执行过程中不能独立完成就需要跳转到动态库中执行,动态库可以给多个程序提供,因此也被称为共享库,动态库不能缺失,该影响的不是一个程序还有可能导致多个程序无法执行
2.静态链接:该链接过程中会直接将自己要使用的方法拷贝到可执行程序当中,以后的程序就不用再依赖于静态库了
在linux中,编译器形成可执行程序,默认采用的就是动态链接,使用静态链接的方式是:gcc 文件名 -o指定文件名 -static
这里我们使用file指令去查看一下该链接方式
很明显这里显示的是静态链接。
其次我们需要注意一下几点:
1.如果我们没有使用静态库,但是我们一定要进行静态链接,这个行为是不行的
2.如果我们没有动态库,只有静态库,而且我们的gcc能找到,那么我们是可以进行动态链接的,这是因为gcc默认优先动态链接-static的本质也就是改变优先级
3.我们的可执行程序不一定是纯的动态链接或者静态链接,而是混合的
4.在设定时就规定我们的动态链接只能使用动态库,静态链接只能使用静态库
那么说了那么多关于动态库,与静态库的想关内容,那么对比于动静态库其都有什么优势劣势呢?
动态库因为是共享库,所以有效的节约资源(磁盘空间,内存空间,网络空间)【优点】
动态库一旦缺失会导所有程序无法运行【缺点】
静态库不依赖于库,程序可以独立运行【优点】
但是静态库体积大消耗资源【缺点】
总结
这里给大家简单的总结一下,我们以程序运行的四个阶段出发给大家介绍了我们编译器在各个过程中执行的任务,可想而知,我们的编译器在我们的程序运行当中是非常重要的一部分,但是程序的一整个实现,运行以及调试过程,我们所需要使用的不仅仅只是我们的编译器,更多的还需要其他工具相互配合使用,那么对于后续如何,我们下回再见。