目录
强烈建议全文阅读!
gcc:专门编译C语言
g++:编译C语言和C++
1、设计样例
gcc如果不是9.3.1版本,可能不支持for循环
编译时要加上-std=c99才可以#使用C99的标准
gcc xxx1 -o xxx2 :将xxx1文件编译为 -o紧跟的文件名xxx2文件
写C++代码文件后缀不能写错
要么是xxx.cpp
或者是.cc文件
xxx.cxx也可以
gcc不能编译CPP代码
但是g++和gcc编译语法是一样的
为了支持更高级的语法选项,编译时可以加上-std=c++11#使用C++11的标准
mv xxx1 xxx2#重命名xxx1
2、程序的翻译过程
翻译过程:预处理、编译、汇编、链接
预处理:去注释、头文件展开、条件编译、宏替换
编译:检查语法,把C语言变成汇编语言(报错:语法报错、链接报错、运行报错)
汇编:将汇编语言编译成为二进制目标文件(这种二进制目标文件没办法直接执行)
链接:链接库文件,将之形成可执行文件
分步编译:
第一阶段:预处理
gcc -E xxx -o test.i#形成临时文件
从现在开始进行程序的翻译,预处理完就停下
但是翻译出来的.i文件有几百行,多出来的行是哪里来的?
是从头文件展开出来的,同时还拷贝了该文件自带的头文件
也就是说,经过预处理之后,头文件也就没用了
而一般来说,我们在配置g++时间,会把相关的头文件和库文件都放在了/user/include文件目录下
因此,我们在windows系统下下载的vs2022编译器,实际上也会伴随着下载配套的头文件
打开.i文件和源文件对比:去掉注释、宏替换、头文件展开
在不同的编译器版本,有些代码内容可能不一样
那么就意味着要维护多个代码版本
这会增加维护成本
所以,为了解决这个问题
就增加了一个叫做条件编译的东西
通过条件编译,就可以只维护一份源代码
对应不同的场景可以在编译阶段进行动态的裁剪
那部分不需要编译,那部分需要编译
通过动态的条件编译之后
一份源代码就可以适应多个不同的场景使用
增加了源代码的容错以及维护的成本
这就是条件编译
条件编译格式:
#define XXX
#ifdef X1
...
#elif XX2
...
#else
...
#endif
...
通过定义宏的方式对代牧进行动态的编译裁剪
同时了,可以通过命令行的方式对文件进行编译处理:
gcc -DXX=1 file#命令行添加宏
第二阶段:编译:变成汇编语言
(也可以从.c文件开始,只是多了一步预处理)
gcc -s test.i -o test.s
-s的意思:从现在开始进行程序的编译,编译完成就停下来
第三阶段:汇编:将汇编语言翻译成为二进制目标文件
gcc -c test.s -o test.o(.o的意思是.obj的意思,object file 可重定向目标文件)
-c:从现在开始进行程序的汇编翻译,汇编完成就停下来
第四阶段:
链接:
gcc test.o -o my.exe(一步到位)
预先处理、编译、汇编:-ESc -iso
程序的翻译为什么是这个过程?
在最刚开始的时候,编程都是用打控制带进行的
有孔,透光,就为1;没有孔,不透光,就为0
通过一条纸带,给机器输送二进制指令
但是,后来的人们觉得很麻烦,所以就做了汇编语言(例如各种注记符,本质上就是二进制指令)
再往后的发展,就有了变成语言,例如C语言
同时,只要不是二进制代码,机器就看不懂
所以,就需要将对应的语言翻译成二进制代码
例如,有汇编的编译器,有语言的编译器
但是,编译器的书写是很难的
我们是可以从编程语言代码直接编译到二进制代码
但是,很麻烦,尽管技术上可以解决
到那时,如果这样做,那么前面的阶段
即汇编语言到二进制代码的编译成果就完全没有用上
几乎等于从零开始开发
有必要吗?完全没有必要。
所以,为了利用上前人的成果
我们选择了从编程语言编译成汇编语言,再从汇编语言编译成二进制代码
所以,总的来说
今天的我们都站在巨人的肩膀在看世界
这也是一种历史的惯性发展
这就是为什么编译是这样的过程
但是:第一个汇编语言怎么编译成二进制代码呢?
第一个汇编语言写出来的时候,又没有对应的编译器。就算我发明了汇编语言,也无法编译成为二进制代码
那么将毫无意义
所以,到底是先后汇编语言还是先有编译器呢?
肯定是先有人用二进制写了一部分汇编语言
先有语言,才会有将汇编语言翻译成为二进制代码的冲动
同时,用二进制写的汇编语言的编译器本质也是一个软件,编译的软件
这个时候,我就可以再用汇编语言的编译器软件写一个纯汇编语言的编译器
也就是说,我现在这个汇编的编译器是用汇编语言写的,而不是二进制写的
那么,二进制写的汇编语言的编译器就不需要了
同时,当C语言出来以后,首先要用汇编语言写一个C语言的编译器
然后,再用C语言的编译器写一个纯C语言的C语言编译器
就这样,汇编语言写的C语言编译器也没用了
上述这个过程,叫做编译器自举的过程
刚听这首歌的时候,我还是一个小男孩,现在,我已经是两个孩子的妈了
3、链接-动静态链接-各自特点和区别
一、是什么?
是一个把我们的程序和库结合在一起的过程
语言一定要有自己的标准库
ldd XXX.exe#查找文件到底依赖的,是哪一个动态库。(这个链接的动态库,是二进制的文件,而且是在系统装好了的)
它会将可执行文件所依赖的动态库给展示出来
如果是C语言,链接的就是C标准库
那么,库里面是什么?有什么?
例如我们使用的printf、malloc等等方法函数的实现,都是打包在这个C标准库内的
我们写了一个printf、本质上就是一个接口调用而已
也即是说,有些方法我们不需要自己书写了,而是直接进行调库即可
但是,有那么多的方法,成千上万,我怎么直到要用哪一个呢?
所以,就有了各种头文件XXX.h
这个头文件就相当于一个实现方法的说明书
例如,math.h里面,这个头文件告诉我们:
它包含着各种相关的数学的方法,以及各个方法对应的参数、返回值等,方便我们调用
所以,事实上,我们在安装C语言开发环境的时候
还需要安装的有对应的编译器、C标准库和C头文件
我们在安装Vs时,那个选择开发环境的界面,事实上选择的就是C标准库和C头文件
所以,链接就是:
把我们的可执行程序编译链接,然后和标准库进行链接,最后形成一个可执行的目标文件
这个库又是什么呢?
是大佬给我们写好的各种方法的集合,用库和头文件的方式让我们调用
Linxu:(库的真正的名字:libc.so.6)
动态库:.so
静态库:.a
windows:
动态库:.dll
静态库:.lib
二、为什么?
1、提高开发效率和安全性(库是大佬写的,所以效率性、安全性更高)
2、站在巨人的肩膀(库是大佬写的,所以你不用再写,你也不会写)
三、怎么办?(怎么做到链接的?)
Linxu:(库的真正的名字:libc.so.6)
动态库:.so
静态库:.a
因为存在两种链接库,所以存在两种链接方式:即动态/静态链接
动态链接:(默认的链接方式)
在编译链接的时候,编译器就会将目标库的地址,也就是动态库的地址给好
我们执行某一个文件的编译链接
当运行到某一行需要动态库的时候
就会按着这个地址去既定的位置找到这个动态库
拿到对应的方法,并且执行,传参的传参、返回的返回
拿到结果后,继续执行后面的代码
这样的链接模式,会导致的是,一旦动态库缺失,那么所有和这个库进行动态链接的程序都无法执行
甚至编译错误
而静态链接是什么呢?
和动态相对的
就是不再去相应的动态库去调取方法了
而是在编译的时候,直接把库中的方法直接拷贝,干到自己的可执行程序中
直接在自己这里实现了
所以,就不再需要去进行动态库的链接
所以,谓之静态
动态库 && 优缺点:
1、不能丢失
2、只需要备份一份,即可共享所有的文件(节省资源)
静态库 && 优缺点:
1、一旦拷贝形成,和动态库无关
2、浪费资源
形成静态可执行文件的编译:
gcc -o mytest-static test.c -static
静态库在哪?
默认情况下,Linux下,一般静态库都是默认没有安装的
怎么安装?百度搜索,全都有
Linux最全环境配置 + 动态库/静态库配置 - 知乎 (zhihu.com)
静态链接应用场景?
要求程序具有强大的跨平台性时,就用到了