文章目录
- 一、gcc vs g++
- 二、预处理
- 三、编译
- 四、汇编
- 五、链接
- 六、动态库和静态库
- 七、动态链接和静态链接
一、gcc vs g++
1.gcc是c语言编译器,g++是c++/c语言编译器
2.因为c++兼容c语言所有g++可以编译c语言,不过还是以c++的方式进行编译
gcc编译的c语言所形成的二进制只更接近于c风格
3.gcc/g++使用选项完全一致 ,本文以gcc为例
4.选项:
4.1编译概念:将源代码翻译成二进制可执行程序的整个过程称作编译
4.2gcc/g++后面直接跟源文件默认是将源文件进行编译,最后形成的可执行二进制文件
会在当前目录下形成a.out,如果想要最后形成的可执行二进制文件的名字是其他名字可以
使用-o选项来定制可执行二进制文件的文件名
4.3-o选项,指定最后形成的文件的文件名
4.4-E选项,源文件开始要进行编译了,但是做完预处理之后就停下来,如果此时没有-o
选项默认将结果输出到屏幕文件中,如果由-o选项则创建新的文件(以-o后面的文件名)
然后将结果输出到该文件当中,该文件以.i结尾
4.5-S选项,源文件/.i文件要开始进行编译了,作完编译工作就停下来,如果没有-o选项
指定输出的新文件名。那么就会使用该源文件的文件名后面添加.s形成一个文件,在当前
目录下,这种文件以.s作为后缀
4.6-c选项,源文件/.i文件/.s文件要开始进行编译了,做完汇编工作就停下来,如果没有-o
选项指定输出的新文件名。那么就会使用该源文件的文件名后面添加.o形成一个文件,在当
前目录下,这种文件以.o作为后缀,这种文件称作可重定位二进制文件
4.7gcc xxx.o -o code 将可重定位二进制文件进行链接最后形成code可执行二进制文件
如果没有-o选项指定输出的新文件名。那么就会形成一个a.out可执行二进制文件在当前
目录下
二、预处理
1.头文件展开:将头文件的内容拷贝到当前源文件当中(当头文件是自己的头文件并且该
头文件在当前目录下时,使用gcc编译源代码无需指明该文件的路径,因为gcc会自动去
当前路径下寻找该头文件)
2.宏替换:#define 定义的宏在预处理阶段会将符号转为真实的值,宏函数就转换为对应的
逻辑
3.去注释:将注释信息全部删除(剪裁源代码)
4.条件编译:将不满足条件的代码全部裁剪掉
5.条件编译的使用场景:
此时公司上架了一款软件,这款软件分为免费版和付费版,免费版有5个功能,收费版有20
个功能,那么此时公司在完成这个项目到上线再到维护这个项目,需要维护一份代码还是
两份代码呢?肯定是维护收费版的代码就可以啊,因为如果要维护两套,那么从代码编写
到测试再到出bug再到后期维护都需要对两套代码进行维护修改,太浪费资源了,此时只
需要维护收费版一套代码就可以解决问题,因为免费版的功能付费版都拥有,那么此时的
问题就是收费版的功能是比免费版的功能要多的,那么如何使用这一份代码上线两种版本
呢?使用条件编译即可,这样就可以使用同一份代码编译出两套软件
三、编译
1.作用:将源代码/预处理过后的代码(也就是c语言本身)翻译成汇编代码
2.为什么要将c语言代码转换成汇编代码?编译器自举又是如何做成的?
2.1在计算机最初发展的时候,编程使用的都是打孔纸袋,因为打孔纸袋可以通过是否透过
光线来标识二进制,也就是说最初的编程是使用二进制进行编程的,因为计算机是二进制的
那么为什么计算机是二进制的呢?
2.2计算机为什么是二进制的?为什么计算机可以识别二进制指令或二进制数据?
因为计算机的硬件就两种状态有无或者是高低,硬件的状态就是两态的,那么使用二进制去
表示就是最简单的表示方法,而计算机之所以可以识别二进制是因为在设计cpu时就会设计
出对应的指令集,本质上就是认为规定了什么二进制指令对应什么样的操作,然后将这个
规则按照一定方式焊到cpu上形成cpu的指令集,这样我们就可以通过二进制来操作cpu
进行一系列操作,而我们所写的代码最终就是翻译成cpu指令集中的指令,然后cpu就可以
根据这一堆指令挨个进行执行,最后完成我们想要的目的
2.3随着计算机的发展,计算机编程总是使用打孔纸袋是很麻烦的,从键盘中一致输入二进
制都很麻烦还容易写错,更别说拿个指代打孔来表示二进制进行编程的,更是麻烦+容错率
高(一个孔位没弄好,就需要从头再来),所以人们在思考一个问题,能不能创造一门语言
来进行编程这样更适合人来接受,于是汇编语言就诞生了,二进制编程是不需要编译器的
但是汇编语言需要编译器了,cpu又不认识mov什么什么的,它只认识二进制,那么就需要
再创造出汇编语言对应的编译器
2.4此时拥有了汇编语言,需要汇编语言编译器,但是又不能使用汇编语言去编写汇编语言
的编译器,因为计算机不认识汇编语言,所以只能首先使用二进制去编写汇编语言的编译器
可想而知这是多么大的工程,二进制编写出来汇编语言的编译器之后,此时就用拥有了汇编
语言和汇编语言编译器,那么就可以在汇编编译器上使用汇编语言进行编写软件,而编译器
本身就是一个软件啊,所以就可以使用汇编语言在二进制编写的汇编语言的编译器上编写
汇编语言的编译器,那么此时该编译器就是由汇编语言写的,更加好维护,此时就可以做到
使用汇编语言编写的编译器来进行编译汇编语言 --- 这称作编译器自举
2.5随着时间的发展,c语言诞生了,那么c语言的诞生就需要将c语言翻译成cpu可识别的
二进制指令啊那么c语言的翻译规则也从二进制从头再来搞出来吗?这样太麻烦了工程量太
大本身二进制就复杂c语言本身就复杂,叠加在一起,很难直接从二进制开始搞翻译啊,但
是此时我们拥有汇编语言啊我们可以将c语言先转变成汇编语言,而汇编语言转变成二进制
已经由对应的技术了,那么此时在翻译c时直接将它翻译成汇编语言不就直接结束了
2.6此时c语言产生了,c语言的翻译方式也是翻译成汇编语言就可以,那么此时就需要c语言
的编译器,但是都不认识c语言啊,无法使用c语言去写c语言的编译器按啊,但是此时有汇
编语言,就饿可以使用汇编语言写出c语言的编译器,然后在汇编语言写的c语言的编译器
上使用c语言去开发c语言编译器,此时就完成了c语言的自举
3.通过上述的论述过程我们可以发现,创造出语言必须有语言对应的编译器,那么现有
语言还是先有编译器呢?我们学习语法时,其实就是在学习这一门语言的规则,而编译器
进行翻译时也是根据规则来翻译的,那么就是说创造出编译器是需要规则的,也就是在创
造编译器之前规则就已经诞生了,语法就已经诞生了,所以是现有语言有了对应的规则,
才能去创造出对应的编译器,此时也可以感知到学习语法其实就在学习编译器的翻译规则
四、汇编
1.汇编:将源/预处理/编译文件编译成二进制文件,以.o结尾,我们称作可重定位目标二进
制文件
2.此时汇编就将代码彻底转变为了二进制文件,那么这种二进制文件可以运行吗?
不可以,这种二进制文件是不允许执行的
原因:源文件中会使用到外部函数(非本文件定义的函数),在当前文件中只有这些函数的
声明,没有这些函数的实现,这些函数可能是自己在其他文件中编写的函数也可能是库函数
但是无论是哪一种函数此时只有声明没有定义,因此是不可以被执行的,只能再通过链接器
和其他源文件或者是去找到对应的库找到对应的函数的实现并进行链接,才可以执行
五、链接
1.链接:将多个源文件和库链接起来,因为编写代码时可能一个源文件使用到的函数的定义
在其他源文件当中或者在库中,而链接过程就是将当前源文件中使用到的函数的定义找到,
(在其他源文件或者库中找到对应的函数定义并链接起来)
然后标记一个相对地址,加载到内存时再将函数真实的地址填写上去
六、动态库和静态库
1.库:在编写代码时,例如我们使用到了printf或者scanf函数,这些函数不是我们自己定义
的,包含头文件时也只是包含了printf和scanf函数的声明,并没有它的实现,而它的实现
是存放在库中的,库中存放的就是:函数编译好的二进制机器指令以及变量数据(二进制)
2.在Linux中动态库以.so为后缀,静态库以.a为后缀
在windows中动态库以.dll为后缀,静态库以.lib为后缀
3.动态库和静态库都是库,里面存储的都是函数编译好的二进制指令或者变量(二进制形式)
之所以叫法有区别是因为在链接时这两个库的链接方式不同
4.头文件中只包含函数的声明,函数的实现在库文件当中
七、动态链接和静态链接
1.动态链接:
1.1编译器将源代码编译为目标文件,连接器进行动态链接:不嵌入库中对应函数的二进制
指令,仅在文件中记录"库的路径、函数名符号",最终生成最终的可执行程序
(所以如果没有对应的动态库就无法链接形成可执行二进制文件)
(生成可执行文件时,动态库文件必须存在,链接器需要验证符号,但代码不复制到可执行文
件中 )
1.2程序运行阶段:
加载可执行文件到内存
检查依赖的动态库
加载动态库到内存(映射到虚拟地址空间)
只解析少量必须的启动符号(如动态链接器自身需要的函数)
跳转到main函数开始执行
第一次调用printf时将对应的函数二进制指令加载到内存当中,然后再将函数地址填入
并且第一次调用就会将后续所有用到的printf全部写入函数地址
1.3动态库加载过程
虚拟地址空间:整个库文件被映射到进程的虚拟地址空间
物理内存:按需加载实际访问的页面
2.静态链接:
2.1静态库的结构
静态库(.a文件)本质上是多个目标文件(.o)的集合包
每个.o文件包含一个或多个函数的实现
链接器只提取程序实际用到的.o文件
2.2链接过程
(1)编译器将源代码编译为目标文件(.o)
(2)链接器扫描目标文件,收集所有未解析的符号
(3)在指定的静态库(.a文件)中查找这些符号
(4)找到包含这些符号的目标文件(.o)只复制程序用到的目标文件(.o)
(5)将这些目标文件的二进制代码复制到最终可执行文件中
(6)解析符号引用,完成地址重定位
(7)生成完全独立的可执行文件
3.动态链接的优缺点:
3.1内存中不可能只有一个程序在跑,肯定有多个程序在跑,那么使用动态链接,只需要
将所需要的函数的二进制指令加载内存即可,这样多个程序共用这一段二进制指令,具有
共享性以及节省内存
3.2但是动态链接时是发生在程序开始执行时才进行,所以在加载动态库到内存,然后再
确定库函数的实际地址,然后进行库函数跳转以及库函数返回是比较麻烦的,比较浪费时间
3.3动态链接并不是将函数的二进制指令嵌入到目标当中,所以当使用动态链接形成可执行
程序之后,该程序依然依赖该动态库,如果将该动态库删了,那么可执行程序便无法执行
4.静态链接的优缺点:
4.1静态链接是在链接阶段就直接将静态库中对应函数的二进制指令嵌入到可执行程序当中
这样即使后续静态库不存在了,可执行二进制文件依然可以执行,因为不依赖静态库
4.2在运行程序时,不需要再去库中寻找对应函数的二进制指令,可以直接执行,比较快
4.3但是内存中不可能只有这一个程序在运行,那么多个程序在运行时并且多个程序都共同
、 使用了同一份库函数,那么同样的二进制指令就需要拷贝到做个二进制文件中,拷贝多次
这样会导致很浪费内存,因为有大量的重复代码
