写在前面:
上一篇博客, 我们学习了vim编辑工具,学会了怎么写代码,
这篇文章,我将分享代码该怎么编译的问题。
目录
写在前面:
1. gcc和g++介绍
2. gcc是如何编译程序的
1. 预处理
2. 编译
3. 汇编
4. 链接
3. gcc的选项介绍
4. 我们使用的函数是哪来的
5. 我们的.o文件和库是如何链接的?
6. debug和release
写在最后:
1. gcc和g++介绍
我们通过gcc和g++编译代码,gcc主要编译C语言,g++则可以编译C语言和C++,
gcc和g++的选项基本一致,所以我介绍gcc的选项,g++直接平替过去就行。
这里我们话不多说,直接演示一下:
比如说,我们写了一段代码:
我们可以用gcc来编译一下:
我们发现,使用gcc编译的时候,他会生成默认叫做a.out的可执行程序文件,
其实具体是什么名称或者说后缀并不重要,能不能执行,
只取决于他有没有可执行权限,我们在权限那篇文章已经讲过了。
当然我们也可以写C++代码,C++的后缀名有不少,
可以用.cpp,不过我更习惯用.cc作为我的后缀名:
比如说我现在写了一段C++代码,用g++编译一下:
编译的使用,默认形成的可执行程序也是a.out。
补充:
g++是可以直接编译C语言的,他就直接按照C语言的形式编译。
2. gcc是如何编译程序的
1. 预处理
预处理主要干这几件事:
a. 去注释
b. 头文件展开
c. 条件编译
d. 宏替换
这是我们文件先在包含的一段代码,存在注释,头文件,条件编译和宏替换。
我们需要使用gcc的一个选项:-E
以及一个选项 -o 指定我们形成的文件的名字。
对这段程序进行预处理操作:
我们成功形成了文件,打开看看:
我们可以看到,左边的使我们的源代码,右边是预处理之后的程序,
可以看到,预处理该干的事情都干了:
这个就是预处理的过程,我们学习C语言的阶段应该是学习过了。
补充内容:
我们为什么能在Windows或者说Linux下进行C/C++或者其他形式的开发呢?
我们系统中一定要提前或者后续安装上,C/C++开发相关的头文件,库文件。
C/C++开发环境不仅仅指的是vs,gcc,g++,更重要的是语言本身的头文件和库文件。
所以Linux是提前给我们装好的C/C++的头文件,
那么在哪里呢?
这个路径就是gcc,g++在Linux下默认搜索头文件的时候,搜索的路径。
我们能在这里找到我们常用的头文件。
我们可以vim一下进入stdio.h文件里面看看:
总共就是九百多行,我们刚刚展开的头文件,也是差不多这个行数。
其实他就是把这一段拷贝到了我们的源文件当中。
所以我们在下载所谓的VS2019这些编译器,选择开发包的时候,
就是在同步下载C/C++对应的头文件和库文件。
补充内容:
gcc是支持对文件进行文本级内容修改的,
比如说:
我们编译的时候,定义一个RUN的宏:
这个宏确实是定义进去了。
2. 编译
编译形成汇编代码:
这些其实就是汇编代码。
3. 汇编
再将汇编代码转成机器可以识别的形式,
说人话就是形成二进制:
我们就能看到这些意义不明的二进制乱码了。
当然,我们也可以用一些二进制查看工具:
可以查看一下二进制的内容:
4. 链接
最后我们就完成了:
获得了可执行程序。
3. gcc的选项介绍
-o 选项,
我们刚刚已经对-o 选项有了一个基本的了解,
实际上,他形成的自定义的文件名我们是可以随便修改的。
这里我就不举例子了,我命名的是mytest ,你们想叫什么都行。
只要在-o 后面跟上我们的可执行程序即可。
那么,
我们对程序进行预处理的时候,使用的 -E 选项是什么意思呢?
作用:告诉gcc,从现在开始进行程序的翻译,将预处理工作做完后就不要往后走了。
那 -S 的选项又是什么意思呢?
从现在开始进行程序的翻译,将编译工作做完就停下来。
那 -c 的选项是什么呢?以此类推:
从现在开始进行程序的翻译,将汇编工作做完就停下来。
而汇编形成的test.o文件,又称为可重定位目标二进制文件,简称目标文件。
(其实Windows下的.obj文件就是目标文件)
而这个文件是不能独立执行的,需要通过链接才能执行。
最后进行gcc链接,
其实就是将可重定位目标二进制文件,和库进行链接形成可执行程序。
他们的选项:-E -S -c ,如果你记不住的话,可以看看键盘左上角的ESC键(记得c是小写就行)
4. 我们使用的函数是哪来的
比如说这段代码里的printf函数:
我们想要调用一个函数,我们使用函数名来调用,
我们需要看到函数的声明,printf这个函数的声明在stdio.h这个头文件里面,
那他的定义呢?或者说这个函数的实现呢?是谁给我们提供的?
实际上是有一个库来给我们提供方法的实现。
这里用的是C语言,所以其实就是C语言的标准库来提供实现。
我们通过这个 路径就能找到这个C语言标准库,
所以C语言标准库本质上其实就是一个文件。
而在Linux下,.so结尾的是动态库,.a结尾的是静态库。
而在Windows下:.dll后缀是动态库,.lib后缀是静态库。
一般来说,库是有自己的命名规则的:libname.so.xxx,
所以上面的libc,c就是名字,.6是他的版本。
方法的实现就是在库当中的,
库其实就是把源文件,经过一定的编译(翻译),然后打包,只给你一个文件即可。
总结:
头文件提供方法的声明,库文件提供方法的实现 + 你的代码 = 你的软件。
(不用我们重复造轮子,站在巨人的肩膀上)
5. 我们的.o文件和库是如何链接的?
有两种方法:
1. 动态链接
2. 静态链接
先说动态库,
动态库也叫做共享库,是多个程序共享的一个库,他们是构建了一种链接的关系。
比如说我们上面刚刚演示的那个C语言的库,如果我们把它删了,
那我们的这个可执行程序就无法执行:
我们用C语言写的可执行程序,默认调用的都是动态库,就都没办法执行了。
再来说静态库,
在使用静态库的时候,实际上是把静态库的内容也拷进了程序里面,
这样使用的时候就不需要通过链接去找库资源,以后也就不用依赖静态库了,
而是可以自己使用了。
现在我们写代码验证一下:
这是我们之前直接用gcc编译出来的可执行程序,
可以看出:
在Linux中,编译形成的可执行程序,默认采用的就是动态链接 -- 要求系统提供动态库
我们使用:gcc test.cc -o mytest_static -static
使用静态链接编译一个程序,
可以看见他没有动态链接,
我们通过 ll 查看更多信息:
可以明显看出静态链接的可执行程序体积变大了,
在Linux中,如果要按照静态链接的方式,形成可执行程序,需要添加-static选项,
也就是系统需要提供静态库才能使用。
不过我们Linux下默认只装了动态库,静态库需要自己安装。
输入:sudo yum install -y glibc-static
就能直接安装C语言的静态库。
输入:sudo yum install -y libstdc++-static
就能直接安装C++的静态库。
补充:
1. 如果我们没有静态库,能不能使用-static进行编译呢?不能
2. 如果我们没有动态库,但是有静态库,能不能直接gcc?能
gcc默认优先动态库,如果没有动态库也会用静态库编译,-static的本质:改变优先级
3. 其实我们使用的这些库,不一定是纯的全部动态或者静态链接,是混合的。
总结:
动静态库的优缺点:
动态库是共享库,有效的节省资源(磁盘空间,内存空间,网络空间等)
静态库,不依赖库,程序可以独立运行,但是体积大,比较消耗资源。
6. debug和release
debug版本是我们平时写的版本,release版本是上线版本,
而debug版本可以被追踪调试
那debug为啥能被调试呢?
因为在形成可执行程序的时候,添加了debug信息。
我们可以在使用gcc的时候加入 -g 选项添加调试信息:
gcc test.cc -o mytest_debug -g
可以看见他的可执行程序也变大了一点点,
其实就是里面添加了调试信息。
这里再介绍一个指令来查看我们可执行程序二进制的构成,
来观察我们往里面添加的调试信息:
我们输入:
readelf -S mytest | grep -i debug
查看mytest的调试信息,可以观察到是啥都没有的,
而输入:
readelf -S mytest_debug | grep -i debug
我们可以直观的看到这些调试信息。
写在最后:
以上就是本篇文章的内容了,感谢你的阅读。
如果感到有所收获的话可以给博主点一个赞哦。
如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~