Linux编译器-gcc/g++使用

🪐🪐🪐欢迎来到程序员餐厅💫💫💫
          主厨:邪王真眼
主厨的主页: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

我们学过c语言的都知道,从c语言源代码到最后的可执行程序要经过以下是四个阶段
  1. 预处理(进行宏替换)
  2. 编译(生成汇编)
  3. 汇编(生成机器可识别代码)
  4.  连接(生成可执行文件或库文件)

我们可以通过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
  1. 选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程
  2.  选项“-o”后接生成的文件的文件名
  3. “a.i”文件为已经a.c预处理后生成的文件

注意:在预处理之后,我们的文件发生了很大的变动,但是,他依旧是C语言文件,因为编译器没有进行语言之间的转化

可以看到,右边的预处理扣的文件直接暴增到八百多行,除此之外还有一些别的变化,我们一一分析
  1. 头文件展开:增加的这个八百多行就是原本我们的<stdio.h>里的东西
  2. 删除注释:我们可以看到原本在a.c中被注视掉的哪一行printf函数在a.i文件中不见了,这是因为在预处理阶段编译器会删掉注释(毕竟注释是给程序员看的,又不是给编译器看的)
  3. 宏替换:a.c中的#define N 100  宏不见了,而且后面prin头发里的M变成了100,这就是对宏的替换
我们再来看看条件编译
我们先把a.c重写一下
接着预编译得到文件a.i
我们发现main函数里除了注释还少了很多东西,这就是条件编译的作用
我们先来翻译一下a.c的条件编译
#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 把代码翻译成汇编语言。
用户可以使用 “-S” 选项来进行查看 , 该选项只进行编译而不进行汇编 , 生成汇编代码。
实例 : gcc –S a.i –o a.s

我们可以看到现在代码已经变成了汇编语言


汇编

汇编阶段是把编译阶段生成的 “.s” 文件转成目标文件
读者在此可使用选项 “-c” 就可看到汇编代码已转化为 “.o” 的二进制目标代码了
实例 : gcc –c a.s –o a.o
我们打开文件发现是一堆乱码,这是因为我们的vim编辑器是对文本文件进行处理的,用它直接打开二进制文件就会显示乱码

gcc下的链接

在成功编译之后 ,就进入了链接阶段,完成了链接之后 ,gcc 就可以生成可执行文件.
要将我们刚才创建的a.c、a.i、a.o 、a.s变成可执行文件我们只需要使用gcc 文件名就行,如果要指定生成的可执行文件名字就加上 -o 【你改的名字】
实例: gcc a.c –o a.exe

我们要知道在window操作系统会根据文件后缀区分文件。但是linux不会,你可以随意指定后缀,但我们最好还是按window下的规则,因为这样也方便我们自己观看

链接作用

我们在a.c中调用了printf函数,我们知道要使用一个函数分为 函数声明、函数定义、函数调用 三个步骤
我们在main中进行函数调用、在头文件中包含了函数声明,那么函数定义呢?他在哪
最后的答案是 :
系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了 , 在没有特别指定时 ,gcc 会到系统默认的搜索路径“/usr/lib” 下进行查找 ,即可 链接到 libc.so.6 库函数中去 , 这样就能实现函数“printf” , 而这也就是链接的作用。

静态库和动态库

库的后缀

在windows下动态库的后缀是.dll,静态库的后缀是.lib

linux下动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。

库的命名:libname.so.XXX

注意库本质上就是一个文件,有到这个文件的路径,它就是把很多个源文件给你打包编译成一个文件了,达到既可以使用功能又可以隐藏源文件的目的。
事实上我们的linux系统中是下载了很多库的,也正因此我们才可以编写代码
  • 静态库

是指编译链接时 , 把库文件的代码全部加入到可执行文件中 ,
缺点:生成的文件比较大
优点:在运行时也 就不再需要库文件了。
  • 动态库
在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时 链接文件加载库,当程序执行到指定的代码段,就会去动态库内部查找对应的内容。
优点:这样可以节省系统的开销。
缺点:一旦丢失,所有链接该库的程序都无法执行了
  • 查看库
1.我们可以通过下面的指令查看该文件连接了哪些库
ldd 文件名
第二行的结果就表明我们链接了c标准库
2.我们可以使用file指令查看,输入
file a.exe
这里的 dynamical linked就是动态链接,说明我们用的是动态库
  • 使用静态库
如果我们想要生成静态链接的文件,则额外加上选项-static
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工具


🥰创作不易,你的支持对我最大的鼓励🥰
🪐~ 点赞收藏+关注 ~

e3ff0dedf2ee4b4c89ba24e961db3cf4.gif

  • 25
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值