🪐🪐🪐欢迎来到程序员餐厅💫💫💫
主厨:邪王真眼
主厨的主页:Chef‘s blog
所属专栏:青果大战linux
总有光环在陨落,总有新星在闪烁
gcc概述
GCC是GNU Compiler Collection的缩写,是一款自由软件、跨平台的编译器。它支持多种语言,包括C、C++、Objective-C、Fortran、Ada等,可运行在多种操作系统上,如Linux、Windows、macOS等。GCC可以将源代码编译成目标代码,并链接成可执行文件。除此之外,GCC还提供了许多优化选项,可以对生成的代码进行优化,使程序的性能得到提升。GCC的源代码也是开源的,任何人都可以自由地查看和修改它
gcc是GCC中的c语言编译器,而g++是GCC中的c++编译器,二者的用法几乎一模一样,所以我们今天只讲gcc
- 预处理(进行宏替换)
- 编译(生成汇编)
- 汇编(生成机器可识别代码)
- 连接(生成可执行文件或库文件)
我们可以通过gcc生成他们所对应的文件,从而了解各个部分的作用。
但是,为什么呢?为什么是这四个步骤呢?这就涉及到编译器自举了。
编译器自举
计算机语言的发展
我们都知道计算机刚出来的时候是没有c、c++、java这些语言的,那时候我们是通过打孔的纸条来对计算机输入数据的,如果有孔就是0,没孔就是1,与二进制相对应。但是这种方式的效率太低了,后来行业里的大佬便发明了汇编语言,基于此,操作系统、编译器这些东西才逐渐出现,再后来人们又对效率感到不满,于是c语言诞生了,最后就是一生二,二生三、三生万物,不计其数的编程语言被发明出来了。
编译器的发展
要知道,计算机只能看懂二进制语言,后来发明的汇编c语言什么的虽然让程序员用着爽了,可是人家计算机根本看不懂啊,于是编译器登场了,它就是连起人和计算机的一座桥梁。对于最早的语言汇编来说,我们用二进制语言写出编译器,它的功能就是把各种汇编代码转化为二进制使计算机看懂,在这之后我们的汇编就可以被计算机看懂了、可以用来编程了,于是我们又用汇编写了汇编的编译器。同理,在C语言出来之后,我们无法用C语言写一个C语言的编译器,所以就用汇编语言写一个C语言的编译器。现在C语言就可以被计算机编译了,于是我们就可以再用C语言写一个C语言的编译器。
这个时候就出现了一个问题,我们的C语言编译器是直接把C语言转化为二进制,还是C语言转化为汇编,汇编再转化为二进制呢?有人或许会说:直接转化二进制多好,一步到位了啊,但是我们要知道二进制是非常反人类的,直接把C语言转化为二进制是一件很困难的事,但汇编相对于二进制就简单很多,而且汇编转二进制的部分前人也已经写好了,于是我们最终选择了第二种方法,这即是为什么C语言有预编译,编译,汇编这三个过程
编译器自举的过程大致分为两步,第一步是用已有的编译器编译出一个最简单的版本,第二步是使用这个最简单的版本来编译出完整的编译器。这样就可以用新生成的编译器替换旧的编译器,从而实现编译器的更新和升级。
gcc的编译过程
我们先创建测试文件a.c,并且写入一些内容,如下所示
预处理
预处理功能主要包括头文件展开、去注释、宏替换、条件编译
gcc –E a.c –o a.i
- 选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程
- 选项“-o”后接生成的文件的文件名
- “a.i”文件为已经a.c预处理后生成的文件
注意:在预处理之后,我们的文件发生了很大的变动,但是,他依旧是C语言文件,因为编译器没有进行语言之间的转化
- 头文件展开:增加的这个八百多行就是原本我们的<stdio.h>里的东西
- 删除注释:我们可以看到原本在a.c中被注视掉的哪一行printf函数在a.i文件中不见了,这是因为在预处理阶段编译器会删掉注释(毕竟注释是给程序员看的,又不是给编译器看的)
- 宏替换:a.c中的#define N 100 宏不见了,而且后面prin头发里的M变成了100,这就是对宏的替换
我们再来看看条件编译
#ifdef N//如果N被宏定义了那就执行下面的代码
printf("%d\n",N);
#elif M //如果M被宏定义了且N没有被宏定义那就执行下面的代码
printf("%d\n",M);
#else//若果MN都没有宏定义那就只想下面的代码
printf("No define\n");
#endif//条件编译结束
在预处理阶段,编译器会根据条件编译的结果对源代码进行删除或保留从而得到新文件
在LInux下,我们可以在命令行中定义宏
gcc a.c -o a.i -D M=1
他的作用等价于在a.c开头写了#define M=1
我们现在来思考一个问题
现在我开发了一款软件,我设置了免费版的和收费版,收费版的会有更多更好的服务,免费的只有一些基本功能,那么问题来了,难道我要维护两份源代码吗?要知道维护的代码越多成本肯定越高
这时候既可以用到条件编译,我们把免费的和收费的有区别的地方用条件编译包起来,这样维护时只需要维护完整的代码,而发行免费版本就用条件编译发型阉割版的,收费的就用条件编译法完全体
因此条件编译的最重要的应用场景就是一份代码发行多个版本的软件。
编译
在这个阶段中 ,gcc 首先要检查代码的规范性、是否有语法错误等 , 以确定代码的实际要做的工作 , 在检查无误后,gcc 把代码翻译成汇编语言。
我们可以看到现在代码已经变成了汇编语言
汇编
gcc下的链接
在成功编译之后 ,就进入了链接阶段,完成了链接之后 ,gcc 就可以生成可执行文件.
实例: gcc a.c –o a.exe
我们要知道在window操作系统会根据文件后缀区分文件。但是linux不会,你可以随意指定后缀,但我们最好还是按window下的规则,因为这样也方便我们自己观看
链接作用
静态库和动态库
库的后缀
在windows下动态库的后缀是.dll,静态库的后缀是.lib
linux下动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。
库的命名:libname.so.XXX
注意库本质上就是一个文件,有到这个文件的路径,它就是把很多个源文件给你打包编译成一个文件了,达到既可以使用功能又可以隐藏源文件的目的。
-
静态库
是指编译链接时 , 把库文件的代码全部加入到可执行文件中 ,
优点:在运行时也 就不再需要库文件了。
- 动态库
在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时 链接文件加载库,当程序执行到指定的代码段,就会去动态库内部查找对应的内容。
- 查看库
ldd 文件名
file a.exe
- 使用静态库
gcc -o a.exe a.c -static
但是不出意外,你的操作系统会报错
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status
这是因为你还没有下载静态库到linux中,我们需要手动下载,记得是root权限
yum install -y glibc-static libstdc++-static
我们分别用静态库和动态库链接同一个文件看看区别
[qingguo@iZf8z6fhz4n89uhtqx9ey6Z ~]$ gcc -o a.exe.static a.c -static
[qingguo@iZf8z6fhz4n89uhtqx9ey6Z ~]$ gcc -o a.exe a.c
可以发现静态库的链接结果是动态库的100倍!!!可以看出它对空间的消耗是真的大
对它使用ldd,发现确实不是动态库
[qingguo@iZf8z6fhz4n89uhtqx9ey6Z ~]$ ldd a.exe.static
not a dynamic executable
再用file看看
[qingguo@iZf8z6fhz4n89uhtqx9ey6Z ~]$ file a.exe.static
a.exe.static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=498e5403fe6a8ef2f42ab2f6b0d70ef20db36827, not stripped
更是明确的告诉你是静态链接
今天学习了gcc的预编译、编译、汇编、链接四个功能,还补充了编译器自举和静态库、动态库的知识,记得好好复习总结,下一期我们来学习makefile工具