【Linux】gcc、g++编译器
目录
- gcc和g++编译器
- c、c++程序的编译过程
- 预处理
- 编译
- 汇编
- 链接
- 动态库
- 静态库
- 头文件与库文件
- 动静态库的优缺点
- 静态库的安装
- 编译时指定使用静态链接编译
- 查看文件信息
- 查看文件的共享库动态依赖关系
- 可执行文件的debug和release版
- 动静态库的匹配原则
gcc和g++编译器
我们在Linux中,可以使用gcc和g++编译器对对应的语言进行编译,gcc只能编译c语言的代码,g++既能编译c也能编译c++,因为c++兼容c语言。但是因为要兼容c语言,g++在一些地方的编译性能上可能有损失,所以如果是纯c语言的代码,就不要用g++,用gcc就行。c++的话用g++。我们使用编译对应代码的方法也很简单,使用如下指令:
gcc C语言源文件
g++ c++源文件
就能完成代码的编译,gcc与g++的用法可以说是一模一样。我这里写了一段代码并使用g++编译器进行编译,
可以看到系统自动生成了一个有可执行权限的文件,我们可以使用文件名直接运行。
注意这里必须使用./a.out的方式才能执行,直接使用a.out系统会当成指令去系统指定的目录下查找,就会报错。我们可以输入echo $PATH指令查看系统指定的路径,这些路径下的可执行文件会在输命令时被系统找到。我们自己要执行可执行文件就要指定目录。
下面是一些gcc和g++的选项:
-E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
-S 编译到汇编语言不进行汇编和链接
-c 编译到目标代码
-o 文件输出到 文件-static 此选项对生成的文件采用静态链接
-g 生成调试信息。GNU 调试器可利用该信息。
-shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
-O0
-O1
-O2
-O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-w 不生成任何警告信息。-Wall 生成所有警告信息。
这些选项下面也会用到。
c、c++程序的编译过程
预处理
预处理过程也被称之为预编译过程,预处理的过程会做的事情有:去注释、处理预编译指令包括头文件展开、宏替换、条件编译。我们可以使用gcc、g++编译器生成只进行过预处理的源文件,
gcc -E c语言源文件
g++ -E c++源文件
这是我们会发现并没有生成对应的文件,而是直接将编译后的结果打印了出来,我们必须要在后面加上-o 想要生成的文件名 才行。我们使用-E选项编译器只执行预处理阶段,不进行编译、汇编或链接,不会生成对应的文件,默认就会将结果输出到标准输出也就是屏幕。
gcc -E c语言源文件 -o 想要生成的文件名
这时我们会发现生成了一个test.i的文件,这里最好就以.i结尾,Linux文件系统本身并不会根据文件的后缀给文件定性,Linux只看文件性质也就是我们使用ll指令看到的参数给文件定性,但这并不代表文件后缀没有用,文件后缀可以给到系统提示作用,这里的.i结尾就表示他是一个仅预处理后的文件,我们后续可以使用g++编译器对其进一步处理,而如果不用的话系统没有收到提示就会以为这是一个普通文件,就不会进一步处理了,这里我们后续会试验一下。
我们再看向生成好的.i文件,
我们与源文件的内容对比,
会发现注释去掉了,头文件也展开了所以文件大了许多,然后条件编译也完成了,宏替换也做了,这就是预处理阶段会做的一些事。
我们可以在g++指令时就使用选项实现定义好一个宏,这样就可以触发条件编译的选项,指令如下,
g++ [选项] 要编译的文件 -D要定义的宏
可以看到这次就变成打印debug了,其实不止这一个选项可以这样用,g++指令都能使用这种方式来预先定义宏从而在编译时调整条件编译的可能,这种方式也可以被用在实际当中,比如不是会员就限制软件的一部分功能。
编译
编译工作主要将预处理完的代码翻译成汇编代码,我们可以使用指令:
g++ -S 要编译的文件 -o 要生成的文件
生成仅编译过的文件, 编译器处理到翻译这一步就停下来,我们以.s为后缀表示处理到翻译这一步的文件,这里不论是.c还是.i编译器都能进一步处理成.s文件,但是这是在文件以.c或.i结尾的情况下编译器才能正常识别,不以这两个结尾编译器就认不出来,
当然我们也能主动声明,
g++ -S -E c++ 要编译的文件 -o 要生成的文件
-E c++可以主动声明这是一段c++代码,可以使用g++编译。
翻译中后的代码就是一段段汇编了。
汇编
汇编过程主要将汇编代码生成机器可识别的机器码,此时的代码就已经是二进制文件了,我们已经看不懂了。.o为后缀的文件叫可重定位目标二进制文件,简称目标文件。
g++ -c 要编译的文件 -o 要生成的文件
使用od指令查看二进制文件
可以查看代码情况。
链接
虽然.o文件已经是二进制了,但是还是不能执行,需要进行链接才行,使用选项-o选项完成链接步骤,
g++ 要编译的文件 -o 要生成的文件
因为这一步之后就是可执行程序了,所以这里的要编译的文件是源文件的话与不加选项的相比就达到了自定义命名的效果。
那到底链接的过程在干什么呢?我们在进行函数的链接,不仅是自己的函数,更重要的是库中的函数,我们在不论是Linux还是win抑或是mac中写代码,不仅仅要安装编译器,更重要的是还要安装语言本身的头文件,库文件。我们使用的那些函数比如printf什么的就是C语言标准库本身自带的函数。像c语言的标准库,它的本质就是一个文件,是有路径的,在centos下,c语言的标准库文件在 /usr/libc64 下。以.so结尾的是动态
库,以.a结尾的是静态库。我们日常使用的win下也有动态库与静态库,动态库以.dll结尾,静态库以.lib结尾。
当然,我们没有经过配置的主机应该是没有静态库的,下面讲一下什么是动态库和静态库。
动态库
动态库又称共享库,为什么叫共享库呢?因为他是所有程序共享的。动态库在系统中只有一份,所有用到这个库的都得找到这个文件,就像一辆公交车,所有人都可以乘坐,我们系统默认采用的就是动态库。
静态库
静态库与动态库正好相反,他是私有的,即每个人都有单独的一份。就像私家车,车都是一样的,但是不能共享,个人用个人的。
头文件与库文件
头文件在预处理阶段就已经包含,库文件则是在连接时被寻找。这两个文件里面到底有什么呢?头文件与库文件共同组成一个体系,即头文件中是函数声明、宏定义、类型定义这些东西,相当于一个提前声明,事先说明我里面到底有些什么,免得编译时因为没有函数声明直接报错,而库函数中则是真正的函数使用方法,当然这是已经经过处理打包成二进制文件的形态了。库函数在预处理时就被包含,确保编译的正常通过,而在经过了一系列的处理之后,源文件(.c)变成了目标文件(.o),此时到了链接时系统就会找到对应的库,确保库中的函数存在并做好一系列准备工作确保实际能够运行,这样可执行文件就完成了。对于动态库,系统不会将它直接包含进可执行文件中,而是在运行时进行跳转,对于静态库,它会被实际包含进可执行文件中,不再依赖静态库本身。
动静态库的优缺点
动态库是所有人共享的,毫无疑问,他省内存,这也是系统默认使用它的主要原因,但是与之而来的就是所有程序都依赖这个库,这个库要是没了,所有程序就都跑不了了,我们Linux的指令大多都是用c语言写的,所以c语言的库其实挺重要的,要是没了,很多指令就都用不了了。
笔者这里斗胆删除了,很多指令都是用不了了,因为我是云服务器,重置很方便,不方便重置的读者就不要尝试了哈,知道这么做很危险就行了。静态库个人用个人的,显然,它不依赖静态库,你就算把静态库删了照样能跑,但是它真的很浪费空间。动静态库之间的优缺点正好是互补的,但是明显动态库更优,你依赖动态库,那你就依赖呗,我不删就行了,我闲的没事删它干嘛,省出来内存那可是实打实的啊。
静态库的安装
Linux中默认应该是没有安装静态库的,我们可以手动安装一下,使用指令:
sudo yum install -y libstdc++-static
安装c++静态库
sudo yum install -y glibc-static
安装c语言静态库
编译时指定使用静态链接编译
Linux默认使用的就是动态链接,怎么使用静态链接呢,在编译指令后面加上-static指令就行。
可以看到,不管是静态链接还是动态链接,程序都能正常运行,但是静态链接的编译出来后大了不少,ll出来的文件信息的第5个参数是文件大小(单位字节)。
查看文件信息
在Linux中我们可以使用file指令查看文件信息,
file 文件名
我们对刚生成的可执行文件使用,可以发现信息中也是标出了这个文件是动态链接(dynamically linked (uses shared libs))还是静态链接(statically linked)。
查看文件的共享库动态依赖关系
使用ldd指令可以查看可执行文件的动态库依赖关系,
ldd 文件名
可执行文件的debug和release版
在Linux中使用gcc和g++默认生成的文件是release,我们可以在编译指令后面加-g生成debug版,debug版为了方便调试,加入了很多调试信息,
可以看到,相比较release,确实大了。但远没有静态链接的大。
我们可以使用readelf指令查看可执行程序的二进制构成,
readelf -S 文件名
使用该指令配合grep可以查看debug信息,可以看到加了-g的有debug信息,而没加的就没有。
动静态库的匹配原则
在Linux中如果我们没有对应的全部静态库,我们无法使用-static选项,-static实际上就是强制要求全部实行静态链接
而如果我们只有静态库没有动态库,我们依然可以使用编译选项,也就是在不加-static选项时,其实是默认优先使用动态链接,如果没有,就使用静态的,都没有就报错。实际上在我们的程序中,都是动静态混用的,一个可执行程序有的库是动态链接有的则是静态的。