目录
前言:
上篇linux博客中我们了解了yum、rzsz、vim相关的工具,这篇博客我们继续对linux当中的工具进行了解学习,linux操作系统相关的知识是一门庞大的知识体系,希望大家保持定力,从一点一滴做起,不积硅步、无以至千里,大家加油!本篇博客我们主要了解学习Linux下编译代码是如何编译的,即gcc/g++,最后我们介绍一下动静态链接(动静态库)相关的知识点
补充知识:
删除一个用户的操作是:userdel -r XXXX(用户名)
相信不用想,能这样做的只有大BOSS(root)
一、Linux编译器-gcc/g++
背景知识:C语言当中程序变成可执行文件要经历几个阶段,我相信大家已经是再熟悉不过了,前面C++入门篇中函数重载部分我们复习了一遍,在这里我们再进行复习,希望经此,这个知识点我们不要再遗忘,想要具体了解的请关注之前博客:C语言-了解程序环境和预处理看这一篇(超详解)
这里简单介绍,程序处理需要经过以下几个阶段:
- 预处理:头文件的展开/宏替换/条件编译/去掉注释
- 编译:检查语法,生成汇编代码
- 汇编:汇编代码转成二进制代码
- 链接:将多个目标文件链接到一起
那我想在Linux下看每个阶段做的这些事该如何做呢?此时gcc/g++就闪亮登场
1.1 gcc/g++分阶段处理
1️⃣预处理:
预处理:头文件的展开/宏替换/条件编译/去掉注释
执行语句:gcc –E test.c –o test.i
解释:
- -E:该选项的作用是让 gcc 在预处理结束后停止编译过程。
- -o:是指生成目标文件
观察下面代码:
1 #include<stdio.h>
2
3 #define Max 10
4
5 int main()
6 {
7 //测试注释
8 printf("hello world1\n");
9 //printf("hello world1\n");
10 //printf("hello world1\n");
11 //printf("hello world1\n");
12 printf("hello world1\n");
13 printf("hello world1\n");
14 printf("hello world1\n");
15
16 //测试条件编译
17 #ifdef CSDN
18 printf("hello csdn!\n");
19 #else
20 printf("我不是csdn\n");
21 #endif
22 //测试宏
23 printf("宏:%d\n", Max);
24 return 0;
25 }
可以观察到头文件那条语句已经没了,包括宏也已经替换到它所在的位置,条件编译也已经进行判断保留应有的语句,注释也已经删除。 不难发现头文件展开后包含了大量的绝对路径,而代码调用库函数中的接口就是通过这些路径进行查找。
补充知识:
我们可以发现条件编译在预处理阶段进行处理了,其实它可以通过命令行的方式对条件编译的条件进行传参,不知道是操作系统版本的原因还是编译器版本的原因,反正在我的服务器命令行下不可以操作😵,大家可以自己操作一下。
小问题一个:
头文件的意义是什么?
头文件最大的意义就是:1.写代码 2.支持代码自动补齐! (头文件当中存放了大量函数的声明,具体的实现在对应的库当中,因此在我们编写代码的环境当中一定存在对应的头文件和库)
2️⃣编译(生成汇编)
编译:检查语法,生成汇编代码
执行语句:gcc –S test.i –o test.s
解释:
- -S:就是只进行编译而不进行汇编,生成汇编代码
可以看到已经变成了汇编代码,上篇C++博客中我们就了解了call指令,这里调用了printf函数最终转成汇编代码就有了call指令。
3️⃣汇编(生成机器可识别代码(二进制代码))
汇编:汇编代码转成二进制代码
执行语句:gcc –c test.s –o test.o
这是啥?啥也看不懂😅可以以二进制的方式打开这个文件:od test.o
4️⃣链接(生成可执行文件)
链接:将多个目标文件链接到一起
执行语句:gcc test.o -o XXX(自定义)
1.2 gcc选项
- -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
- -S 编译到汇编语言不进行汇编和链接
- -c 编译我们的代码生成二进制代码
- -o 文件输出到 文件
- -static 此选项对生成的文件采用静态链接
- -g 生成调试信息。GNU 调试器可利用该信息。
- -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
- -O0-O1-O2-O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
- -w 不生成任何警告信息。
- -Wall 生成所有警告信息。
上述这么多选型我们如何记忆,事实上并不是所有的选项对于我们都重要,我们现在只需要把程序处理的那几个选项记住即可,至于其它的在以后我们用到了回头查阅即可。对于程序处理的几个选项下面讲述一个小技巧:看一下键盘左上角是不是有个Esc按钮,对没错,它正式匹配了我们程序处理各个阶段的选项,不过是ESc,生成的文件是iso,是相机的感光度😆,帮助我们记忆
汇编阶段生成了二进制代码,欸对了,二进制不就是计算机可以识别的代码嘛,怎么不能执行?相必大家一定知道,不卖关子,我们调用的库函数类似于printf等,它们的实现体现到此时的代码了吗?哎不对啊,你预处理阶段不是头文件展开了吗?怎么能没有呢?你看我们刚才不是看了嘛,头文件展开之后出现了大量的绝对路径,这些路径就是供我们去调用库里的函数的,但是链接之前我们并没有去找这些库里的接口啊,所以到了汇编结束,一直处理的是我们自己的代码,对于我们调用的库的接口并没有链接到一起,所以链接阶段就是把我们自己的代码和库里的代码链接一起的过程。因此引出下面拓展知识:动静态链接和动静态库
二、动静态链接(动静态库)
背景:上面我们讲到链接之前一直处理的是我们自己的代码,库里的接口并没又掺和,所以链接是干嘛呢?链接的本质就是我们调用库函数的时候,和标准库如何关联的问题
2.1 动静态链接
1️⃣动态链接
简单来说动态链接就是:链接标准库时去链接动态库就是动态链接。
🥴啥玩意,你这不是废话嘛,哈哈,下面我们举个简单例子来说明,动态链接就好比,你在宿舍,中午了,你要去吃饭,食堂好远,但你又不得不去,因为学校要求不让点外卖,那你就只能停下手头的工作,去食堂吃饭,吃完饭你再回来,继续你的工作,这个食堂呢,就是动态库,你去食堂的过程呢就是动态链接的过程。总结来说,动态链接就是遇到相应的指令,但是该指令的实现在其它地方,你需要跳转到这个地方,完成对应的功能之后,回退到我们自己的程序当中的过程叫做动态链接,这个链接标准库称作动态库。
见见Linux下的文件是什么链接,如下:
补充知识:
file XXX(文件名):用于辨识文件类型
ldd XXX(文件名):在Linux系统中用于列出可执行文件或共享库所依赖的共享库
linux下库的命名:
动态库:
libXXXXXX.so:lib是前缀,.so是动态库的后缀,去掉后缀去掉前缀就是库的名称如上:
静态库:
libXXXXXX.a:lib是前缀,.a是静态库的后缀,去掉前后缀就是库的名称
动态链接的优点:
形成的可执行程序小,节省资源(内存、磁盘、网络)
动态链接的缺点:
受库的升级或取代的影响,并且影响程序运行的速度
2️⃣静态链接
静态链接:执行程序时,执行到对应的库函数接口,会从库函数中拷贝一份实现代码到我们对应的程序当中,继而继续执行我们的程序,这样的链接方式称之为静态链接。静态链接的库叫作静态库。简单来说就是吃饭的时候人家叫外卖给你送到了宿舍门口。
linux下想要以静态链接的方式生成可执行文件执行下述指令:
gcc test.c -o testmy -static
可以看到静态链接去看链接的共享库它提示不是一个动态可执行,就是说明它把代码拷贝过来了,并且可以观察到静态链接生成的可执行文件大小几乎是动态链接生成的文件大小的100倍了,这就可以体现动静态链接各自的优缺点了。
静态链接的优点:
不受库的升级或者被删除的影响,可以节省一定的效率
缺点:
形成的可执行程序文件太大,每次调用都会实现依次拷贝,造成大量的冗余
几个问题:
1️⃣可不可以删掉系统中的C动态库?
可不敢,系统当中的那些许多指令就是通过动态链接的方式的文件,并且通过上面可以观察到Linux下默认链接的方式就是动态链接,并且这个库有且只有一份,所有用C语言写的程序都要用到这个库,因此动态库又称作共享库。
2️⃣静态链接拷贝的是.so内部的代码吗?
不是,上面我特别提到静态链接链接的是静态库,这也就意味着,动态链接链接动态库,静态链接链接静态库,它们互相不掺和。
3️⃣系统本身,为了支持我们编程给我们提供了什么?
系统给我们提供标准库的.h(头文件)(告诉我们怎么用),标准的动静态库.so/.a(告诉我们,方法实现我有,来找我!),因此可执行程序 = 我的代码 + 库的代码。
三、总结
本篇博客我们主要学习了解了 Linux 中的编译器 gcc/g++ 的工作流程及选项,以及动静态链接的知识。 工作流程分为预处理(展开头文件、宏替换等)、编译(生成汇编代码)、汇编(生成二进制代码)和链接(生成可执行文件)四个阶段,并给出了各阶段的执行语句示例。常用选项有 - E、- S、- c、- o 等,分别用于控制编译过程的不同环节。动静态链接部分简要提及了动态链接和静态链接的概念。这些内容为理解和使用 Linux 编译器提供了基础指导。希望大家有所收获!