【Linux】手把手教你从零上手gcc/g++编译器
🦄个人主页:修修修也
🎏所属专栏:Linux
⚙️操作环境:Xshell (操作系统:CentOS 7.9 64位)
编辑
目录
📌什么是gcc/g++
gcc 与 g++ 分别是 gnu 的 C & C++ 编译器 。gcc可以编译C语言程序, g++既可以编译C语言程序, 又可以编译C++程序, 因为在语法上C++兼容C语言。
gcc/g++ 在执行编译工作的时候,总共需要4步:
1. 预处理,生成 .i 的文件 [预处理器cpp]
2. 将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]
3. 将汇编变为目标代码(机器代码)生成 .o 的文件 [汇编器as]
4. 连接目标代码, 生成可执行程序 [链接器ld]
编辑
📌gcc操作选项
• -E 只激活预处理,这个不生成文件,需要自己手动把它重定向到一个输出文件里面
• -S 编译到汇编语言不进行汇编和链接
• -c 编译到目标代码
• -o 文件输出到目标文件
• -static 此选项对生成的文件采用静态链接
• -g 生成调试信息。GNU 调试器可利用该信息
• -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统有动态库
• -O0 / -O1 / -O2 / -O3 是编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
• -w 不生成任何警告信息
• -Wall 生成所有警告信息
📌gcc/g++是如何工作的
注:本文中会使用大量Linux中的指令操作还有vim文本编辑器的使用,如果对这些还不太了解的朋友推荐先点击文章底部的文章推荐了解一下使用Linux方面的知识。
🎏预处理——宏替换
• 预处理功能主要包括宏定义替换,头文件包含,条件编译,去注释等。
• 预处理指令是以#号开头的代码行。
• 选项[ -E ], 该选项的作用是让 gcc 在预处理结束后停止编译过程。
• 选项[ -o ]是指目标文件,“.i”文件为已经过预处理的C原始程序。
利用Linux中gcc操作验证这一过程:
首先,我们用vim编写一段test.c的代码:
#include<stdio.h>
#define N 10
#define HELLO
int main()
{
#ifdef HELLO //条件编译,如果定义了HELLO宏,那就执行ifdef后面的程序,否则执行else
printf("HELLO条件编译已被执行\n");
#else
printf("HELLO条件编译未被执行\n");
#endif
printf("hello A: %d\n",N);
printf("hello B: %d\n",N);
//printf("hello : %d",N);
//printf("hello : %d",N);
//printf("hello : %d",N);
//printf("hello : %d",N);
//printf("hello : %d",N);
printf("hello C: %d\n",N);
return 0;
}
然后我们使用gcc的只激活预处理选项,将生成的文件定向输入到新文件test.i中:
编辑
接着使用vim打开test.i文件查看预处理结果:编辑
🎏编译——生成汇编
• 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。
• 用户可以使用[ -S ]选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
利用Linux中gcc操作验证这一过程:
首先我们使用gcc的编译到汇编语言选项,将生成的文件定向输入到新文件test.s中:
编辑
使用vim打开生成的新文件test.s, 可以发现之前的文件已经被转换成了汇编语言:
编辑
🎏汇编——生成机器可识别代码
• 汇编阶段是把编译阶段生成的“.s”文件转成二进制目标文件
• 可使用选项[ -c ]就可看到汇编代码已转化为“.o”的二进制目标代码了
利用Linux中gcc操作验证这一过程:
首先我们使用gcc的汇编到二进制机器码选项,将生成的文件定向输入到新文件test.o中:编辑
然后我们使用vim打开新生成的test.o文件, 发现是乱码:编辑
这是因为vim是文本编辑器, 它只能识别文本内容, 不能识别二进制内容, 那么我们可以选择使用二进制查看工具"od"来查看这个二进制文件:
编辑
🎏连接——生成可执行文件或库文件
• 在成功编译之后,就进入了链接阶段。
• 连接是将可重定位目标二进制文件, 和库进行连接形成可执行程序。
利用Linux中gcc操作验证这一过程:
我们利用gcc来将test.o二进制文件和库连接,生成可执行程序test:
编辑
📌函数库的概念
🎏什么是函数库
在前面我们提到了gcc工作的最后一步: 连接。那么我们为什么要将自己编写的代码和库连接呢?
这是因为,库中有我们调用的C语言函数的定义, 它们三个的关系如下图:编辑
他们的关系可以给大家举个例子:
编辑
也就是说, 我们的C程序中,并没有定义“printf”函数的实现,且在预编译中包含的“stdio.h”中也只有该函数的声明, 而没有定义函数的实现, 系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了, 在没有特别指定时, gcc 会到系统默认的搜索路径“/usr/lib”下进行查找, 也就是链接到 libc.so.6 库函数中去, 这样就能实现函数“printf”了, 而这也就是链接的作用。
编辑
🎏静态库和动态库
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。在Linux中其后缀名一般为“.a”; 在Windows中其后缀名一般为".lib"。
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。在Linux中动态库一般后缀名为“.so”;在Windows中动态库一般后缀名为".dll"。gcc 在编译时默认使用动态库。
看概念有些抽象,我们依然来举个例子,如下图所示:
编辑
根据上面的例子也很容易总结出动态库和静态库的特点:
• 在编译器使用静态库进行静态连接的时候,会将自己的方法拷贝到目标程序中,该程序以后不用再依赖静态库。这个就类似将空气炸锅一次性买回家,后面再需要用的时候就不用再出去租赁了。但是相应的, 空气炸锅放在家里的话也会占据家里的空间, 静态库拷贝到程序里也会导致程序变大。
• 而依赖动态库的程序, 动态库不能缺失, 一旦动态库缺失, 那么依赖动态库的很多文件就会出现问题。这个就类似空气炸锅租赁店突然倒闭关门了, 那么原本依靠租赁空气炸锅的用户就没有办法使用空气炸锅了。
防止有朋友还没有安装C语言/C++的静态库,在这里贴两条安装语句供大家参考:编辑
小tips:
• 如果没有静态库, 就不能使用-static静态连接
• 如果没有动态库, 只有静态库时, gcc会找到静态库然后连接
• 程序不一定是全动态连接或静态连接, 往往是混合使用的
• -static的本质: 改变连接的优先级顺序, 并且要求程序只能全连接静态库, 如果此时不存在静态库, 程序就会报错
🎏验证Linux下的动态库和静态库连接
验证gcc 在编译时默认使用动态库:编辑
我们手动要求程序连接静态库:
编辑
编辑
gcc默认生成的二进制程序,是动态链接的,也可以通过 file 命令验证:
编辑
结语
希望这篇关于 gcc/g++编译器 的博客能对大家有所帮助,欢迎大佬们留言或私信与我交流.
学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!
相关文章推荐
编辑
- 点赞
- 收藏
- 关注作者
评论(0)