# GCC选项+静态库+动态库
<font style="background: MediumSpringGreen">
# 目录
[toc]
# 1.gcc的历史
# 1.1.gcc的名字变化历史
gcc它的原名叫做gnu c compile
啊,那翻译成中文呢就是gnu c语言编译器啊。那言下之意呢就是这个gcc啊,他原先是作为我们c 语言的一个编译器啊,随着这个gcc 的一个发展呢啊,他现在支持更多的这个语言的一个编译了,所以:他就不能再叫这个gnu c 语言编译器了啊!,所以他改名了,改成什么呢?叫gnu compiler collection
!翻译成中文呢就是gnu编译套件啊,取的是每个这个单词的首字母。
gcc -std=c99 solve.cpp
GCC原名为GNU C语言编译器(GNU C Compiler)
GCC (GNU compiler collection,GNU编译器套件)是由 GNU开发的编程语言译器。GNU编译器套件包括C、C++、0bjective-c、Java、Ada
和 Go
语言前端,也包括了这些语言的库(如libstdc++
,libgcj
等)
GCC不仅支持C
的许多"方言",也可以区别不同的C语言标准;可以使用命令行选项来控制编译器在翻译源代码时应该遵循哪个C标准。例如,当使用命令行参数-std=c99
启动GCC时,编译器支持C99标准。
- ⭐️安装命令
sudo apt install gcc g++
『⭐️版本>4.8.5⭐️』 - 查看版本
gcc/g++ -v/--version
gcc --version
g++ --version
2
# 1.2.PPT编程语言的发展
# 1.3.⭐️GCC工作流程
接下来呢再给大家去说一说这个gcc它的一个工作流程!
那么我们刚才啊是不是直接啊使用一个指令啊,gcc 然后呢后面是这个c 代码对吧?那最终呢他会给我们生成一个可执行文件。
实在这个gcc的一个呃底层呢,它不仅仅啊就是说直接给你生成一个啊这个可执行文件啊,那其实呢它会经过几个步骤啊
# 1.预处理器(生成.i
)
啊,比如说有这个.h 点c 啊,d c t p p. 那么它首先呢要干嘛呢? 经过我们这个gcc 里面的一个==预处理器进==行干嘛呢?进行一个预处理啊,那得到的是一个预处理后的源代码。 啊,那么预处理做什么事情呢?啊,一般呢做这个,在这个c 或者c 加加代码里面是不是导入了一些头文件啊,对吧?他会把这些头文件呢进行一个展开,其实就是把头文件里面的内容复制到我们这个源代码当中,对吧? 然后呢呃还有比如说把我们代码当中的注释啊给他删除掉对吧?注释你没有必要保留到这个呃程序当中嘛,对不对啊,最终呢他会给你删除掉啊,然后呢,比如说这个我们。在程序当中啊定义的一些宏,他也会做一些这个宏替换啊。那么这个预处理后的源代码呢,一般呢是以这个点i 结尾的!而且这个里面的代码呢,大家也是能够看得懂的,其实就是文本代码!
# 2.编译器「我的研究方向」(生成.s
)
接着呢,他预处理完了以后啊,他要经过这个==编译器「我的研究方向」==啊,
进行一个编译,编译成这个汇编代码。汇编代码呢,一般是以这个.s
结尾!
啊,那这个里面呢其实你也能够看懂啊,但是呢呃都是一些这个啊英文符号啊,这个英文单词啊如果说你没有学过汇编的话呢! 你是看不懂的
# 3.汇编器as
再往后呢汇编代码呢,它需要经过一个叫==汇编器==啊,进行一个汇编,汇编成什么呢?
目标代码啊,那么目标代码呢一般是以点o 结尾啊。
好,那注意了,他会边成目标代码以后呢,这个目标代码呢你可以理解为就是这个计算机它能够识别的这些!一零一零的二进制指令啊,这个代码啊
# 4.链接器ld(生成.out
)
接下来呢他还要干嘛呢?他还要进行一个连接啊,那连接的话呢他要结合一些,比如说==启动代码
==呀!
- 啊,库代码啊,对,这个库代码我们后面呢会给大家去介绍这个静态库和动态库其实就是指的这个库代码。 好,然后呢还有一些其他的目标代码。啊,那最终呢干嘛呢?通过一个叫链接器的这个工具啊进行一个链接,把它链接成一个可执行程序啊。
那可执行程序呢一般后缀名啊,这个.exe
只是在windows 平台下,对吧?还有在这个啊linux 平台下呢有这个.out
啊的结尾啊。好,那这个是。
- gcc 它的一个工作流程。那现在呢大家应该啊都清楚了,对不对?
g++ solve.cpp -E -o test.i
g++ -E solve.cpp -o test.i也行
g++ test.i -S -o test.s
2
3
4
- 然后可以用Vscode看这个
test.i
- 头文件展开
- 代码非常多,发现
- 发现宏Pi被替换成3.14了
- 注释也被删掉了
下面开始汇编。指令是-c
g++ test.s -s -o test.o
- test.o是二进制文件了。无法看懂了
接下来是链接,
但是其实你这种情况下,可以直接执行了
[root@CentosLinux PLinux]# ./te.o
Hello World
2
原因是:我们前面用的小写的-s
它会对你代码进行一个汇编,然后也会给你生成这样的一个可执行文件。
注意,我们发现
-E
生成.s
-S
生成.i
如果我们直接将-S作用于.c发现!
gcc test.c -S
生成 test.s
2
发现,预编译,也给你做了!
所以,我们先前gcc test.c
它里面做了哪些操作?
- 预处理、编译、汇编、链接
# 2.gcc和g++的区别(2张)
- 一个呢,是『专门』用于去编译这个c 语言
- 一个呢,是专门去编译这个c++语言的啊,大家呢先去理解啊
- 后面呢,会详细的给大家去介绍这两个啊这个工具啊
- 然后呢这个版本啊呃一定要大于这个4.85啊!
- 为什么要大于4.8.5呢?因为呃我们啊后续啊如果说写这个C++程序的话会用到这个c++11的这个它的一些特性
- 啊,新功能啊,那么像这个4.8.5呢啊开始啊,往后他就支持这个啊C++11啊,它的这个。个特性啊,那如果说你呃版本是小于它的话呢,那可能呢就啊你写一些这个c 加加幺幺的一些代码的话呢,他就不能够正确的编译。
- 那默认情况下呢,这个乌邦图啊,它是没有安装的啊!
来自PPT:
- gcc和g++都是GNU(组织)的一个编译器。
- 误区一: gcc只能编译c代码, g++只能编译c++代码。两者都可以,请注意:
- 后缀为
.c
的, gcc把它当作是c
程序,而g++当作是c++
程序 - 后缀为.cpp 的,两者都会认为是C++程序,C++的语法规则更加严谨一些
- 编译阶段, g++会调用gcc,对于C++代码,两者是等价的,但是因为 gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的
- 后缀为
又想到了,Linux下,其实是没有后缀概念的。
- 误区二: gcc 不会定义
_cplusplus
宏,而g++会『或许这个就是,为啥?gcc不能自动和C++程序使用的库连接的缘由?』- 实际上,这个宏只是标志着编译器将会把代码按c还是C++语法来解释
- 如上所述,如果后缀为
.c
,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义
- 误区三:编译只能用gcc,链接只能用g++
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用
gcc/g++
,而链接可以用g++
或者gcc -lstdc++
。记住 - gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段, g++会自动调用gcc,二者等价
- 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用
# 3.GCC常用选项参数
『05-06』
-o <filename>
:指定输出文件名-e
name:-e name
:指定name为程序入口地址-ffreestanding
;编译独立的程序,不会自动链接C运行库、启动文件等-finline-functions,- fno. inline functions
:启用/关闭内联函数-L <directory>
:指定链接时查找路径,多个路径之间用冒号隔开-nostarfiles
:不要链接启动文件,比如crtbegin.o
、crtend.o
-nostdlib
:不要链接标准库文件,主要是C运行库-shared
:产生共享对象文件-static
:使用静态链接 ⭐️-PIE
:使用地址无关代码模式编译可执行文件-XLinker <option>
:把option传递给链接器-Wl <option>
:把option传递给链接器,与上面的选项类似-fomit-frame-pointer
:禁止使用EBP作为函数帧指针-fno-buitin
:禁止GCC编译器内置函数-fno-stack- protector
:是指关闭堆栈保护功能-ffunctionsections
:将每个函数编译到独立的代码段-fdata-sections
:将全局/静态变量编译到独立的数据段
gcc编译选项 | 说明 |
---|---|
-E | 预处理指定的源文件,不进行编译,只进行预处理并把预处理结果输出 |
-S | 编译指定的源文件,但是不进行汇编, 输出编译后的汇编代码文件⭐️ |
-c | 编译、汇编指定的源文件,但是不进行链接,只编译不链接⭐️ |
-o [file1] [file2] / [file2] -o [file1] | 将文件 file2编译成可执行文件 file1 |
-I directory ⭐️ | 指定include包含文件的搜索目录,指定头文件路径⭐️ |
-g | 在编译的时候,生成调试信息,该程序可以被调试器调试,在编译结果中加入调试信息, 就是加入GDB调试器能够识别的格式⭐️ |
-D | 在程序编译的时候,指定一个宏『宏的单词,Drague>> ⭐️ |
-w ⭐️ | 不生成任何警告信息⭐️ |
-Wall ⭐️这个其实是all类型的警告 | 生成所有警告信息,对源代码中的多数编译警告进行启用 |
-On | n的取值范围: 0~3。编译器的优化选项的4个级别, -O0表示没有优化, –O1为缺省值, -O3优化级别最高「O的意思是optimization的首字母」⭐️ |
-l ⭐️ | 在程序编译的时候,指定使用的库⭐️比如gcc demo.c -lpthread -g |
-L ⭐️ | 指定编译的时候,搜索的库的路径。 |
-fPIC 或-fpic ⭐️ | 生成与位置无关的代码,使用地址无关代码模式进行编译 |
-shared | 生成共享目标文件,通常用在建立共享库时 |
-std | 指定C方言,如:-std=c99 ,gcc默认的方言是GNU C |
-O0
:关闭所有优化选项。(前面是大写字母O,后面是数字0)
编译器开发中的选项:
-Os
名字来源是:Optimize for size.
- 参考资料:
gcc12.0.0-UsingtheGNUCompilerCollection
.pdf
# -W警告类型
有1个坑: 这次的选项和-D不一样, -D可以选择和后边的分开与否, 但是这个-W不能和后边分开, 一旦分开就出错!
gcc
的 -W
选项允许启用特定的警告,后面可以跟上不同的警告名称。以下是一些常用的 -W
选项及其含义:
-Wunused
:-Wunused-variable
:未使用的变量。[⭐️]-Wunused-function
:未使用的函数。-Wunused-label
:未使用的标签。-Wunused-parameter
:未使用的函数参数。
-Wuninitialized
:-Wuninitialized
:使用未初始化的变量。
-Wall
:-Wall
是一个整合了许多常用警告的选项,相当于启用了多个-W
选项。例如,-Wall
包含-Wunused
,-Wuninitialized
等警告。[⭐️]
-Wextra
:-Wextra
选项启用额外的警告,包括一些不包含在-Wall
中的警告。
-Werror
:-Werror
将警告视为错误,即编译时如果产生警告,则中止编译过程。[⭐️]
-Wpedantic
:-Wpedantic
选项启用更严格的标准遵循,可能会产生一些符合标准但不推荐的代码的警告。
-Wformat
:-Wformat
:检查printf
和scanf
等函数的格式字符串是否与参数匹配。
-Wreturn-type
:-Wreturn-type
:检查是否所有路径都有返回值,避免潜在的返回值类型错误。
# ⭐️总结: 静态库+动态库的使用
GCC的3个选项
静态库:
1.提供头文件,放在某个文件夹里面,比如./include/
2.然后,你使用这个,需要在你的代码中include那个头文件,然后使用下面命令去编译!
- 除非是当前目录才不需要-L
gcc gcc main.c -o app -I ./include/ -l calc -L ./lib
动态库:
1.提供头文件,放在某个文件夹里面,比如./include/
2.设置动态库文件和环境变量
3.使用下面命令
# 4.静态库
# 1.静态库.a
的「制作」
- 单词:archive,v.归档
这节课呢我们去学习一下静态库的制作啊。 那首先呢我们要搞清楚什么是库。那所谓的库呢,其实就是库文件啊,它呢也是计算机上的一类文件。
我们可以简单的把库文件看成是一种代码仓库啊,也就是说这个库文件里面呢保存的都是代码啊,当然了,这个地方保存的代码呢并不是说我们能够直接看懂的这个文本代码。而是一些这个二进制的代码。那库呢他提供给使用者一些可以直接拿来用的变量函数或者类等等。这个呢就是库文件,那库呢它是一种特殊的程序啊,
那我们编写库的程序啊和这个编写一般的程序啊,他的这个区别啊不大啊,只不过呢这个库啊它不能单独的运行啊,因为他是要提供给别人去使用的。
那库文件呢有两种啊,一种呢称之为静态库啊,另外一种叫做动态库啊,也叫共享库。
这个静态库和动态库他们的区别是什么呢?
区别呢就是静态库啊,他在程序的链接阶段啊被复制到了程序当中,最后呢再到这个链接,链接成这个可执行程序,对吧?
那么其实这个静态库和动态库啊都是在这个链接阶段做的一些处理啊。
1、那静态库呢在这个链接阶段呢就复制到了我们啊使用它的这个程序当中啊
2、而这个动态库呢,他在链接阶段呢并没有复制到程序当中啊,那什么时候啊到这个程序当中呢,其实动态库呢它也不会啊被复制到这个程序当中。但是呢,加载到这个内存当中,然后呢程序去使用它,去掉它啊。所以呢这个在程序运行的时候呢,由系统呢动态的去加载到内存当中啊,把这个动态库。加载到内存当中,然后呢供程序呢去调用啊,所以呢我们也称之为动态库,对吧?就这个原因啊,动态的去加载到内存当中。然后呢去使用啊。
那么接下来大家想一想这个库啊,它有什么好处呢?
首先第1个好处呢就是可以做这个代码的一个保密啊,那举个例子,比如说呢啊你开发了一个这个程序啊。 呃,比如说你写了一套算法啊,做这个压缩的对吧?那你是不是要把你的这个程序啊拿去卖钱,对不对?那你卖钱的话呢,你不可能说直接把你的这个写好的源代码呀。发给人家对吧?那这样的话,别人不就知道你的这个逻辑的嘛,对吧?知道你怎么写的吗?是不是啊,那我们可以怎么办呢?我们可以把我们写好的这个程序啊。打成一个库啊,所谓的这个库呢,其实里面就是一些代码嘛,对吧?啊,这些代码呢实现了一些功能啊,那打好成这个库以后呢,你是不是发给别人。然后呢,每一个这个卖多少钱一个卖多少钱是吧?那别人呢他也很难呢去能够知道你这个里面啊程序是怎么写的,对吧啊?
- 那有的同学说,那这个不是有这个啊反编译的这个工具嘛,把你这个程序反边一下,不就能看到了吗?啊,其实对于我们这个c 语言。c ++来说的话,这个反编译之后啊,他的这个代码的还原度呢是非常低的啊。
- 你不像这一个Java语言呢,他如果说啊这个写的代码被反编译以后啊啊他的这个还原度呢基本上能够达到这个95%以上啊。
- 那所以说呢大家也不用担心这个呃如果说你写的这个库。啊,会不会被别人这个反编译啊,其实他的这个还原程度啊是非常低的啊,而且呢啊即使你还原回去,别人呢也要进行一些的这个逻辑的一个处理啊。他得想你这个逻辑怎么写的啊?
第2个好处呢就是方便部署和分发。
- 啊,比如说呢你在这个公司啊,你去写这个程序,你写了这个一百个代码。啊,就是一百个这个原文件是吧,那如果说你要给别人用的话呢啊你可能要把这一百个原代码呢都发给别人是吧?好,那如果说呢你打成一个库啊,那一个库文件。
- 发送是不是更快一点,对吧?方便我们进行一个部署和分发啊
好,那接下来呢我们来看一下这个静态库啊。是怎么去制作的? 那首先呢我们要搞清楚这个静态库啊,它的命名啊命名规则呢你必须要遵循这个规则来啊。
这样呢我们不考虑windows 平台啊。我们就考虑这个linux 平台。 那么lib呢是啊library, library 的一个单词啊,前三个这个字母对吧?表示这个库啊library。
那么他呢是前缀啊,而且呢是固定的啊,就是这么写的。然后中间呢XXX这个呢是库名字啊,这个需要大家呢自己去取啊,然后呢这个后缀呢是.a
它也是固定的。这是静态库,它是以.a
结尾的啊,这是命名规则。
然后呢我们再来看一下这个静态库的制作分为两步。
# 「步骤1」
通过这个gcc 啊。去获取.o
文件啊,比如说你有啊10个这个文件。你要把这10个文件呢打成一个库啊,那你要通过gcc呢获取这十个文件的这个.o
文件。
# 「步骤2」使用ar命令工具
第二步呢,我们将这些点o 文件啊进行一个打包啊,那我们使用的是这个linux 里面一个啊指令叫ar
工具。其实就是archive的意思,对吧?前两个这个字母啊。怎么用呢?就是ar 然后呢,参数rcs,后边是我要生成的库.a
,然后你要把哪些.o
文件打包到库文件里面呢?后面你就跟上这些.o
文件就可以了
ar rcs libxxx.a xxx.o xxx.o
再给大家去简单的解释一下这个参数啊rcs 啊
那么r 呢就是表示啊我要往这个库文件里面呢插入啊,我们这个点o 文件啊,插入点o 文件,这是r 的一个,这个含义。
然后呢c 呢是表示我要去创建这个备存文件啊
其实我们通过这个a r 指令啊创建出来的这个库(libXX.a)啊,其实就是这个备存文件啊
那s 呢其实是表示去创建这个索引啊,因为你这个里面呢它的这个点o 文件的非常多。那通过这个s 呢参数呢,它会生成这些文件的一些索引啊,方便呢去查找啊。
那这个索引呢大家呃如果说学习过这个my s q l 数据库的话啊,应该有一些了解,对吧?所以啊那其实你也可以理解为什么呢?比如说我们去查这个这个字典,对吧?比如说我们传一个汉字啊,首先呢我们要知道这个汉字的一个偏旁,对吧?我们找到这个偏旁,然后呢再根据这个他的一个后面的一个部首啊,它的一些一个符号的一个个数啊,再去查对应的这个,笔画,对吧?哎,查到,然后再去找到具体的这个汉字。是吧那么这个索引其实就是相当于什么呀,相当于这个偏旁对吧?偏旁部首一样的啊,方便我们去查找的啊。
- 那下面呢就给大家去演示一下啊,如何去制作这个静态库。
好,那我们首先呢把这个c alt 和这个library 啊这两个目录啊啊拷贝进去啊,先拷贝进去,一会儿呢给大家去说这两个是干嘛用的啊。 好,然后呢,在这儿呢我们。 let s 零四是不是就有了,对吧?
好,然后呢我们来看一下啊,就是这个calc文件夹 呢啊比如说呢我们自己啊开发了一套这个程序啊或者一套工具。 专门呢是做这个算术运算的,对吧?比如说我写了一个什么呢?写了一个艾特点c 啊,专门做这个加法的对吧?啊,div 一点c 就是做除法。 然后呢,还有这个啊marty 就是惩罚啊,sub 就是减法是吧?里面呢都有对应的这个呃函数在里面啊,这是我们写的一套这个程序是吧? 好,然后这个面里面呢是对它呢进行一个测试啊,这个非常好理解吗?
对不对?那么接下来呢大家来看一下啊,我现在呢我想把我开发的这个功能啊,
==比如说add.c div.c 干嘛呢?打成一个静态库是吧?产生一个静态库供别人去使用,对吧?==那我们应该怎么去做呢?
在calc/下
ls 一堆.c文件
2
那接下来我们要去对他进行一个这个呃制作啊。 那首先呢这个命名的话呢,我们说是以这个iib 开头,然后叉叉叉是它的一个库的名称。
比如说我们叫c a l c。就是计算嘛,对吧?这个库,然后呢.a 是不是后缀啊,是吧?
好,然后呢我们第一步呢要生成这些文件的什么呀?他的一个啊这个点o 啊,那我们用gcc -c 对吧?
也就是说我们==只进行一个汇编,不去链接他==啊。
gcc -c add.c div.c mult.c sub.c
生成了对应的.o文件
2
艾特点odey 一点,然后还有这个market market 点o 还有sub 点o。
==注意:这个head.h的头文件啊,咱们不需要打成这个点o 文件(虽然add.c里面include了他)因为他在这个操作的时候呢啊预编译的时候呢,是不是直接会包含到我们这个文件里面去啊,原文件里面去啊,对不对啊,他会做这个预处理啊,所以说头文件呢不需要啊。==
然后这个main.c的是我们的这个测试的一个代码,也不需要。
- 啊,好,那现在呢有了这些点o 文件了,接下来就是干嘛了,是不是创建这个啊静态库是吧?用这个a r 指令啊,然后加上这个参数rcs。
calc/目录
ar rcs libcalc.a add.o div.o
ls一下看一下
libcalc.a
2
3
4
5
其实这些点o 文件是不是都是一些这个目标代码呀?对吧,它里面都是这些二进制的一些代码啊,就是这些一零一零的这个机器代码。好,那么静态库的制作呢非常简单。
啊,那关键的是这个静态库如何去使用啊,那如何去使用这个静态库呢?
# 2.静态库的「使用」
ar rcs
库文件的名称:lib+库的名称+.a
库文件的名称≠库的名称
当然,你的库的名称随便你起啊,不叫calc也行
2个目录
calc/ 我们上一届,制作静态库的文件
library/ 去使用,calc/制作的静态库代码
mkdir lession5
cp -r calc/ library/ lession5/ 我们拷贝一份
cd lession5/library/
>ls
发现,我们通过去呀去看一下这个目录啊。那这个library呢比如说是我们自己呀写的一个项目啊开发的一个项目,那么一般开发项目呢我们都会进行一个这个分包啊,就是分文件夹,这样便于我们进行一个什么呀开发嘛,对不对啊?如果说你把所有的文件都写到一个目录下,是不是非常的乱啊?
比如
|include
--head.h
|-lib
|-main.c
|-src
|-add.c
|-div.c
|-sub.c
|-mult.c
include里面呢保存的都是一些需要用到的头文件啊这个head.h其实就是刚才calc里面的那个头文件啊。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
lib目录呢其实就是library的意思啊,那很多同学啊去比如说去网上啊去下载一些这个呃这个代码啊,你会发现它里面也会有一个lib目录,对吧?那么这个l i d目录里面放的是什么呢?
大家想一想,==是不是应该就是我们需要使用到的一些库文件啊==,放到这个lib木下啊。 好,main.c呢就是我们测试的文件
然后这个s、r、c呢是我们呃放的一些这个源代码文件啊,比如说艾特点c、d、y点c,mar的点c撒不点c是不是就是呃刚才我们这个calc目录下这些文件啊,对吧?
==啊那么当然了,在这儿呢我们呃目前呢呃不需要使用到他们,对吧?因为我们刚才是不是在calc目录下把那个静态的一个库给制作出来了,对吧?==
那这样的只是让他。大家呢去知道啊,我们这个s、r、c里面放什么,源码的意思,对吧?
# 展示
继续前面的bash
library下
cp ../calc/libcalc.a ./lib/ 拷贝到当前目录下的Lib文件夹中
接下来呢我们要干嘛呢?
是不是要编译我们自己的程序啊是吧编译我们自己的这个慢点c啊这个程序。
2
3
4
5
- 那在这儿呢要说明一点,就是我们如何去使用这个库文件呢?
- ==注意了,我们使用库文件啊,你把这个库给别人,同时呢你还要把这个库它所依赖的头文件(这里指的是head.h)也要分发给别人。==
大家想想,为什么? 因为如果别人他只拿到你的这个库文件,那他知道你这个库里面有什么东西吗? 不知道,那是不是你得通过啊这个头文件里面的一些函数的声明啊? 别人知道这个里面有哪些这个函数,是不是才能够去使用啊?对吧?导入这个头文件,然后去使用,大家想想是不是这样的,对不对? 所以呢你在分发的时候啊,首先一定要把这个库文件和头文件(库对应的头文件)分发给这个用户啊,不然呢别人是无法去使用的啊。 ==好,这一点呢大家一定要注意啊。==
继续
gcc main.c -o app
报错:没有那个文件or目录 head.h
include "head.h"
现在他他找不到,为什么呢?因为在我们这个代码里面啊,我们是用的这个相对路径相对于当前这个main.c
好,那这个时候如果说我们呃解决的话,怎么解决?
1.一个是你把这个头文件拿到跟这个main.c他同一级的这个梦想,那这样可以便于通过啊。
2.如果说我就想放到这个里面,我还想去啊访问到他,怎么办呢?
这个时候就要用到我们啊之前介绍gcc的时候,它的一个啊选项参数啊参数选项啊,大家来看一下
gcc main.c -o app -I ./include/
-I 就是表示我要查找啊你的这个头文件,或者说包含的这个头文件它所在的路径。那它所在的路径在哪儿?
当前是library目录,其实就是整个这个目录嘛,对吧?
2
3
4
5
6
7
8
9
10
11
12
13
也就是说我编译的时候呢,我在这个main.c里面,我用到了你这个库它里面的东西,对不对? 你虽然给我提供了头文件,但是你具体的这些函数的定义是在这个库文件里面。 那你编译的时候他没有找到,他是不是就报这个错了,是吧? 好,那这个时候呢我们就需要使用一个参数。
- ==库的名称(注意:而不是库的文件的名字)!==
gcc gcc main.c -o app -I ./include/ -l calc
gcc gcc main.c -o app -I ./include/ -l 库的名称(⭐️⭐️⭐️⭐️注意:而不是库的文件的名字)!
-l(小写的l)是指定我们要干嘛呢?加载哪个库,对吧?(⭐️⭐️注意:-l后面然后后面呢你可以加空格,也可以不加空格啊)!!!!
2
3
为什么错?那么注意了这个库的位置在哪?库的位置是不是在这个library目录下?
那如果说我现在回车的话,他是不是还是找不到啊?
找不到这个库啊找不到库,那我们要加上一个-L 那到哪儿去找呢?
gcc gcc main.c -o app -I ./include/ -l calc -L ./lib ⭐️⭐️⭐️⭐️(注意这种情况-l和-L都要,除非是当前目录才不需要-L)
2
- 好,大家看现在是不是就编译通过了?
==那么这个呢就是啊我们这个啊静态库它的一个使用啊==,其实呃说简单呢也比较简单,对吧
啊那说难的话呢其实也没有特别的难,是不是?
-L
指定你这个库它的路径。
# 5.动态库.so
和环境变量
# 1.动态库.so
的「制作」
那动态库的制作啊,其实和静态库的制作呢啊类似啊。首先呢我们要去搞清楚这个动态库它的一个命名规则。那这个命名规则呢。为了两部分,一个呢是linux 平台下啊,它是如何去命名的。另外一个呢是这个windows 平台下它是如何命名的啊,那这样呢我们主要是以这个linux 平台啊为重点。
那这个在linux 平台下的这个动态库啊,它的库文件的名称呢是这样的啊。首先呢lib 这是前缀,然后呢XXX呢是表示这个啊动态库它的一个库的一个名字啊,然后后缀呢是.so 对吧?那么你会发现这个其实跟静态库的一个命名规则。啊,非常类似对吧?只不过静态库它的这个后缀呢是.a结尾的啊,而这个动态库呢它是点s o 结尾前面都是一样的,对吧?
那么啊立本呢就是前缀,它是固定的啊,它其实是library 啊这个单词的前三个字母啊表示它是一个酷嘛,对吧? 然后中间的拆开擦拭库的这个名字,需要我们自己呢去给他起名字啊,然后呢点s o 是它的后缀,这个呢也是固定的。
那么制作的过程呢有2个步骤
# 「步骤1」
啊,==第一步==呢就是通过gcc 啊得到.o 文件啊,其实就是目标文件,对吧?(那么在这里注意了,我们呃得到了这个点o 文件呢,必须是和这个未知无关的一个代码啊和这个位置无关的代码。)
==为什么要得到和这个位置无关的代码呢==?**因为我们这个动态库它的一个原理和这个静态库是不一样的。**啊,那么我们后面呢会详细的给大家去介绍这个动态库和静态库它的一个区别啊,那么怎么得到这个和位置无关的代码呢?==其实非常简单,就是在我们这个命令当中啊,加上一个参数啊,叫做-fpic 或者呢是-f大写的pic啊。==
那么这两个呃有什么区别呢?啊,其实目前来看的话,他们是没有什么区别的啊。啊我们现在用的这个电脑呢,比如说用的是这个英特尔的X86的一个架构,对吧?那么在这个X86架构上的这两个其实啊没有任何的差别啊。那么在其他的一些架构的一个平台上呢可能这两个会有一些区别啊,那目前呢我们啊用哪个都是ok 的啊,这个呢不是我们重点要去关注的一个点啊
# 「步骤2」
那==第二步==呢是通过这个gcc 啊得到这个啊动态库啊.动态库呢我们啊还是通过这个gcc指令啊,加上一个参数叫做-shared
,意思共享的
啊,我们这个动态库呢也称之为这个共享库啊,为什么叫共享库呢?啊,我们后面呢给大家介绍了这个动态库它的一个原理之后呢。大家就能够明白了.—这是动态库的一个制作的一个过程啊,其实也非常简单。
# 演示
那下面呢我们就通过案例呀给大家去演示一下。
//用的还是calc的例子
cp calc library ../lesson06 -r
cd ..
删掉calc和library文件里面的.o和静态库
目前一个calc library
cd calc/
gcc -c -fpic add.c div.c mult.c sub.c
2
3
4
5
6
7
8
生成,我们要对谁生成了,是不是add.c, div.c, main.c,sub.c是吧?其实这个main.c我们不需要。 去生成它的点和文件嘛,对吧?因为这个是我们的这个啊,测试的一个文件啊,
那么这是第一步,第一步就完成了啊,注意了,一定要加上这个杠f p i c 这个参数啊,如果说你没有加上这个参数的话呢。他后面呢你去制作这个动态库,它是制作不了的啊。
好,那么第二步呢是通过这个-shared
gcc -shared *.o -o libcalc.so
这个库的名字也随便取,也可以不是calc
2
3
我们ll一下,看这个.so文件,发现是可执行的文件
好,那现在呢我们这个动态库它的一个制作呢就完成了。**那制作完成了以后呢,我们要给别人去使用,对不对?**好,那给别人使用。我们比如说呢啊我们推到这个上一级啊,然后进入到library 目录。
# 2.使用.so库
去看一下啊,那比如说呢这是我们呃写了一个这个项目,对吧?我们你看我是不是有这个include,这里面呢放在这些。
头文件对吧?l i d 里面呢将来要放这个库文件,对吧?
那现在我们要去使用这个动态库。
- 我们怎么去使用呢?首先你需不需要把你的动态库拿过来,放到这个lib目录下,是吧?好,那我们就去拷贝一下,拷贝什么呢?呃,上一步。
你注意啊,我们有了头文件,就是我们刚才还少说了一步,==就是说我们要去使用这个啊动态库啊,你是不是要把这个动态库和这个头文件都要发送给别人==了,对吧? 分发给别人啊,这个库文件里面呢是我们这些函数或者说这些a p i 的一个具体的一个定义。而这个头文件里面是他的一些这个声明,对吧?
gcc main.c -o main -I include/ -L lib/ -l calc
-L就是指定我要去搜索你的这个动态库它的一个路径啊
-l(小写的L)是指定库的名字(还是一样,可以省略空格)
- ==然后,我们尝试执行,看问题?==
他说找不到这样的一个啊文件或者目录啊,但言下之意就是什么呢?就是说我们虽然指定了这个库的一个。 啊,目录以及这个库的名称,对吧?但是呢他生成了这个可执行文件,我们再去运行的时候呢,哎他找不到。 啊,他找不到这个动态库这个文件,所以说呢他报了这样的一个错误啊。
好,那么呃要怎么去解决这个错误呢?
啊,那这样呢我们就要去看一看这个动态库它的一个具体的一个实现的一个原理啊。那我们==下一节课呢在重点呢给大家去介绍这个动态库它的一个啊加载的一个原理啊==,以及如何去解决啊我们动态库加载失败的一个问题
# 3.动态库加载失败的原因✔️
# 工作原理
- 那么动态库呢跟静态库不一样啊,那动态库呢它在进行链接的时候呢,他不会把这个动态库的代码呀打包到可执行程序当中。
好,大家想一想,我们在对。这个动态库去使用的时候啊,对我们测试程序啊进行一个编译的时候啊,==在这个链接阶段啊,我们这个动态库里面的代码呢不会被打包到可执行程序当中==啊他只会把这个动态库的一些呃信息放到我们的可执行程序当中啊这是动态库它的一个原理。
那么所以呢我们在运行的时候啊我们在运行我们这个可执行程序的时候啊,如果说你使用到了这个动态库,对吧? 那他在你的这个可执行程序里面是找不到动态库的代码呢,所以呢你会发现他就啊报了这样的一个错误,对吧? 他说加载这个动态库啊就是失败了啊因为我们啊如果说你应用到了这个动态库,对吧? 那么我们都知道程序啊他要想运行的话呢,首先要加载到内存当中,对吧?程序都是在内存当中运行的。 那么你在使用这个程序的时候,你使用到了动态库里面的内容,那你是不是应该要把动态库的代码加载到内存当中啊是吧?
所以也就是说我们如果说一个应用程序啊它使用到了这个动态库啊,那么在我们这个程序启动的时候==,如果说你应用到了动态库里面的api,那么它这个时候呢要去先把动态库啊加载到内存当中,==然后呢它才能够去使用。
- 那么我们刚才啊在对我们自己的这个测试程序进行编译的时候,是不是没有报错啊?
对吧?那我说了,你编译的时候呢它只是把这个动态库的一些信息放到我们可执行程序当中,对吧? 啊它并不会去加载我们这个啊动态库的一个内容到我们这个内容。你用到了它里面的内容,那你就要把它加载到内存里面啊。 那这个时候呢他由于找不到这个动态库的一个这个就是找不到这个文件,所以呢他就报了这样一个错误啊。
# 新命令ldd
那至于为什么他找不到这个动态库呢,我们一块说啊。
那么在这儿呢呃我们有一个指令啊叫做ldd啊,翻译过来呢就是列出动态的一个依赖,对吧?
所有的依赖啊。那么这个命令呢可以去检查动态库它的依赖关系啊,我们去看一看,比如说呢在这呢我们是不是有一个面这个可执行程序啊,对吧?
我们使用ltd,后面呢跟上这个呃我们的一个可执行程序的一个名称,然后呢按回车,大家来看一下啊。
诶那在这儿呢他会把这个可执行程序啊他所用到的一些动态库啊给列出来啊。
你看第一个是linux啊,v、d,s,o点s,o点一,对吧? 然后后面呢是这个库的一个内存地址啊就是它的一个内存地址,之前呢是库的名称啊。 那么第二个呢是l、i、b、c、l、c、d、s、o,那这个不就是我们自己写的那个动态库嘛,对吧?制作的那个动态库嘛,因为我们用到了,所以呢他在这儿呢也列出来了。 然后你看后面他说什么他说==not,found,是不是没有找到呀==? 对吧? 然后第三个是列表,libc.so.6啊,那这个呢是呃标准的一个c库啊,然后后面呢也有它的一个地址,你看这是它的一个路径,对吧?然后后面呢是它的一个内存地址啊。 然后下面还有一个啊就是呃l,d,linux啊叉八六杠六十四点。搜点二,那这个呢是用来加载我们这个动态库的==一个啊这个动态库==这是系统提供好的啊
我们通过这个l、d、d命令啊去查看这个可执行程序啊它的一个动态库的一个依赖,发现呢这个l、i、b、c,l,c点so,就是我们自己写的这个动态库,它没有找到,对吧啊
==那我们要知道啊这个程序啊它运行起来以后啊,它是如何去查找这些动态库的,对吧?==
# 定位共享库文件
好,那我们来看一下,那如何定位这个共享库文件呢? 啊当系统加载可执行程序的时候啊,那能够知道其所依赖的库的名字,但是呢还需要知道绝对路径啊,就是我这个库它所具体啊在哪个路径下,因为它要加载到这个内存当中嘛。对吧?
啊我们说刚才我们在编译的时候,他只是把动态库的一些信息打包到这个可执行程序当中,但是动态库的代码没有打包到可执行程序当中,是吧?所以我们在运行程序的时候呢,首先要去加载啊那此时呢就需要系统的动态载入器来获取该绝对路径。
啊这个==动态载入器呢其实就是我们在这儿看到的这个/lib64/ld-linux-x86-64.so.2
==「但是PPT上是ld-linux.so
」
就这个动态库啊,通过他去动态的加载这个啊我们写了一些其他的这个动态库啊。
好,那么它的加载的一个这个时机啊是什么呢? 加载的时机呢就是当我们程序啊调用了动态库的一些api的时候,比如说我们这个动态库里面呢有一个add方法,对吧?当你去执行到这个。add方法的时候,他就会去加载啊他就会去加载。 好,那具体怎么加载的呢?
啊大家来看一下。 他说对于这个e、lf格式的可执行程序啊,是由这个l、d,linux点so来完成的啊那他呢先后搜索这个e、f文件的啊dt啊、pass段啊,也就是说呃他在用这个动态载入器啊来获取啊我们这个动态库它的一个绝对路径的时候啊,它有先后顺序啊
# ⭐查找的先后顺序
那怎么查找呢?
「1」首先是DT_RPATH的啊
那这个东西呢,它是在我们程序啊,运行起来以后啊。我们一个程序啊运行起来以后呢,它其实就是一个进程。
啊那么这个linux系统呢会为这个每一个进程啊分配这个虚拟的一个地址空间啊,那其实这个DT_RPATH呢==其实是在我们这个虚拟地址空间里面的啊==
那么如果说我们没有把这个动态库的一个路径啊放进去的话呢,其实它是找不到的啊
- 而且呢这个内容呢我们也改不了啊也改不了,所以说这个呢我们不考虑,大家了解一下就ok了啊。
找不到呢它就找谁呢?
「2」找这个环境变量啊,环境变量就是LD_LIBRARY_PATH
那如果说你这个环境变量里面啊,配置了你这个动态库它的一个完整的路径的话,那么它就会啊根据你这个路径去把这个动态库加载到内存当中啊。
那如果没有的话他怎么办呢?
「3」他接着往后去看这个/etc/ld.so.cache
这个文件里面啊。
看这个文件里面有没有你指定的动态库的一个路径啊
那如果它里面也没有的话啊
「4」那他会去找这个/lib/
目录,或者呢是user/lib
目录啊
然后找到以后呢哎再把这个数据呢:就是把这个动态库的内容啊加载到内存当中。
好,那这节课呢给大家呢去分析了一下啊我们这个动态库它加载失败的一个原因啊。
那分析出来这个原因以后呢,接下来我们就应该知道如何去解决这个问题了,对吧?其实解决问题非常简单,是不是?就是在这个里面或者在这些配置文件里面加入你的这个动态库它的一个绝对路径,对吧?那他就能够找到啊,找到以后呢他就能够正常的加载到内存当中去运行啊。
# 4.解决动态库加载失败的问题
对吧那我们这个可知程序它运行的时候呢,需要加载到内存当中,对吧?
1。那当我们去使用这个静态库的时候啊。这个程序呢他不需要去查找这个静态库,他所在的一个路径,对吧?因为你本身这个静态库的代码就已经打包到了这个可执行程序当中啊,对不对?那你这个可执行程序你运行其实就已经把这个静态库当中的代码呀加载到内存里面了。
2。好,那么动态库它不一样,对吧?你动态库呢他没有把这个代码打包到可执行程序当中,对吧?那你这个可敬程序运行起来以后啊。他要去干嘛?他要去查找你这个动态库啊,他的一个地址就是它的一个路径啊,把这个动态库加载到内存里面。他才能够去使用,对不对啊,那这样呢是动态库和这个静态库它的一个区别啊。
3。我们也可以通过这个ldd 这个命令啊。去查看一个可执行程序,它的一个动态库的依赖关系,对吧?
那我们怎么解决呢?
顺序
DT_RPATH
LD_LIBRARY_PATH
/etc/ld.so.cache
/lib/目录,或者是user/lib目录
2
3
4
# ⭐针对DT_RPATH
「没办法」
其实解决非常好办。我们是不是就是在这些里面去配置上啊,那么他再去查找的时候,是不是就能够查找到。 啊,查找到是不是就能够把这个啊动态库加载到内存里面,然后呢去使用它,对吧?好,那下面呢我们就去解决这个问题啊,解决这个动态库加载不到的一个问题啊。
那么首先呢这个DT_RPATH呢,我们不用管啊,我们改我们改变不了。
# ⭐针对LD_LIBRARY_PATH
那么第二个是这个环境变量叫LD_LIBRARY_PATH这个环境变量啊,我们去看一看怎么去啊修改它。 那么这个环境变量大家应该都清楚吧,对吧?啊,这个环境变量的作用是什么呀?比如说呢我们在这呢呃输入一个指令啊ls,那么这个l s 它其实是一个shell命令嘛,是吧?那他怎么找到这个命令呢?啊,你注意了,==其实呢我们这个呃当前啊这个终端啊其实就是一个应用程序啊==,那么这个应用程序启动以后呢,其实你输入这个ls,他其实最终呢会被啊这个shell解析器去解析,对吧?那么这个shell解析器它怎么找到。这个啊指令的一个路径呢?其实就是根据这个环境变量去找的啊,我们可以在这呢输入一个env,大家看一下。
env
那么其实他会先到这个PATH目录下去找啊找这些命令他的一个这个可执行程序啊,这个就是环境变量它的一个作用。
- 那同样的我们在这呢配置的这个环境变量叫LD_LIBRARY_PATH,对吧?==它的作用呢也是这样的==,就是让我们的这个什么呀:==动态载入器啊==,能够去到这个路径下去查找,对吧?他会到这个路径去查去查找啊。好,那我们来啊配置一下啊。
那么配置的话呢,其实我们有几种方式啊。
# 1.第一种方式export
方法
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:XX路径 ⭐
- 工程上
whoway@dell:~/work/auto$ cat source-flang15.sh
export LD_LIBRARY_PATH=/home/whoway/huawei-classic-flang/install/lib:$LD_LIBRARY_PATH ⭐
export PATH=/home/whoway/huawei-classic-flang/install/bin:$PATH
2
3
就是在我这个终端啊,我直接呢去输入一个这个命令啊。export
就是呃添加我们的这个啊叫环境变量啊
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:XX路径
对吧我们要添加的是这个环境变量LD_LIBRARY_PATH啊,这是它的名称。然后后面呢等于是他的值啊,然后呢加上一个==$LD_LIBRARY_PATH
那么这个含义呢,是表示我先获取我原先的这个环境变量的内容啊==,用$
去获取。
(这里面呢我们可以用$
去获取变量的一个值)。啊。好,然后呢后面呢再拼接上你啊,新加的啊,你新加的这个值。
啊,那我们现在要把什么配置进去,是把我们的这个啊当前的就是你用到的这个库,对吧?动态库它的一个路径啊,绝对路径放进去是吧?
就拼接到后面了啊,那这样呢就配置好了,配置好了以后呢,我们干嘛呀?回车。
- 完成后检查、
好,==然后这个时候==大家来看一下我们啊。是不是有一个可亲程序叫面对吧,它里面是不是使用了这个动态库啊,我在这呢使用这个ldd去查看一下。 大家来看一下回车。大家看这个时候啊。
我们这个i i d g lcd s o 这个动态库文件,它查找这个路径,你看是不是就能够查找到的,对不对,之前有没有啊。
前是不是not found,找不到,那我们刚才配置了这个环境变量以后,他是不是就能够找到了?好,现在呢我们去运行我们这个程序啊执行。哎,你看程序是不是就运行好了。
那这个呢是我们第一种配置方式啊。
==缺点==
- 那么第一种配置方式呢,这个我们刚才是直接在这个终端里面配置的啊,当我把这个终端关掉以后啊,比如说。或者说我们重新打开一个终端,然后我们再去运行这个啊这个可执行程序。哎,你会发现又不好这样的一个错误,对吧?原因呢就是在于啊当我们刚才啊。配置的这个环境变量啊,它是在这个终端当中去配置的。当我们把这个终端关掉以后,重新打开一个终端。这个黄金变量呢它就是消失了啊就失效了。他只是临时的。
# 2.第2种永久export
- 那我们应该干嘛呀?永久的去配置这个环境变量吧,对吧?那怎么配置呢?有两种方式啊,
一种方式呢是啊用户级别的配置。
另一种方式呢是系统级别的配置啊。
# 1.用户级别的配置
好,那首先说这个用户级别的配置。那用户级别的配置呢,我们首先呢进入==到我们的。home 目录==下。 好,然后呢大家看一下在这里面呢,它其实是有一个文件
隐藏的文件叫==.bashrc== 那其实呢我们就是配置他就ok 了啊。
那么在这里面呢,他已经有一些这个环境变量啊配置在里面了,我们不要去动它啊,我们先跳到最后一行。 啊,shift 加g 对吧?好,然后呢我们按这个o 啊往下插入一行。 好,那么在这里面呢,我们要怎么配置,是不是就是我们刚才的这个配置方式
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:XX路径
保存并退出啊,==那么注意了,你保存推出以后呢,你还要使这个生效啊==,
. ./.bashrc或者 这样 . .bashrc
- 注意:我们==前面的这个
.
其实相当于source== - 这个点就是source 的意思
所以source .bashrc也是一样的
2
那么更新以后啊,我们再回到这个这个目录下,然后呢,我们ldd 面查看一下,然后可以了,能找到,能运行
# 第3种-永久export-系统级别的
那其实呢还有一个这个系统级别的啊,那系统级别呢你需要有一些这个啊root 权限啊,那再让我们使用这个sudo vim
那系统配置呢就是在这个/etc/profile
这个目录下啊,这个文件回车啊输入密码。
sudo vim /etc/profile
密码+插入
同理,最后一行,export那些
2
3
,保存并退出啊,然后呢,你别忘了你要干嘛呀,source一下啊。
sudo source /etc/profile 不能这么写,那个source前面不需要加sudo
source /etc/profile就可以了!!
2
3
- 以防万一,继续用
. /etc/profile
试一下
啊,刷新一下啊。好,然后呢我们呃测试一下dollar 啊,去获取一下,或者说ach。但是 他是不是有这个内容啊,对吧?但是我发现这个内容好像有点问题啊,我们来看一下是什么问题啊。他说这个。 home. 他是拼接了好多呀。对吧,拼接了好多啊,
那这个问题呢我们啊啊把这个窗口给它关掉啊,然后我们重新去打开一个窗口吧。
啊,有的时候呢,如果说你之前啊配置了这个用户级别的话啊,那他可能会出现一些问题啊。
好,然后呢我们再来试一下。
echo LDXXX就没问题
没有问题啊。然后呢我们去运行一下这个程序。 是不是正常能够运行起来呀,对吧?那这个就啊配置好了。
第二种呢是修改这个etc 下有一个ld.so.cache这个文件啊。
- 做这个实验前,先删掉
etc/profile
里面的最后你先前配置的,以免出问题
那最好呢我们啊还要刷新一下,对吧?
就是source etc/profile
或者说. etc/profile
啊,更新一下啊,然后我们重新打开一个终端吧,防止这个之前呢有影响啊,
所以打开一个好,那接下来配置这个
# ⭐针对etc/ld.so.cache
- 但是,完美修改
sudo vim /etc/ld.so.conf
sudo vim /etc/ld.so.cache
啊,那么你会发现啊,他不允许我去啊通过这个vim 区编辑,==是它里面呢都是一些这个二进制==的数据啊。==那我们不能直接去修改它,我们得间接的需求给他啊==,怎么间接修改它呢?
# 「1」步骤1-转而编辑ld。so。conf-在里面添加路径
- ==我们找一个文件啊,我们要
sudo vim /etc/ld.so.conf
== ⭐️⭐️
输入这个密码。好,那么在这里面呢,我们再去配置啊。 我们怎么配置呢?其实就是啊把我们这个路径啊加在这个里面就ok 了啊。好,那这个路径。好吗?
然后呢保存并退出啊,然后呢我们还要进行一个更新啊,使用一个指令叫做
# 「2」步骤2-使用sudo ldconfig
sudo ldconfig ⭐️⭐️
**回车,好了,更新好了以后啊,**接下来啊再来看一下,测试,OK
# ⭐针对/lib/
目录,或者是user/lib
「不推荐“」
针对`/lib/`目录,或者是`user/lib`
那么第三种方式呢就是呃在这个/lib/ 目录,或者呢是 user/lib下啊,他会去查找这两个目录,对吧?
那我们==只要把什么呀,把我们的这个动态库文件放到这两个目录下,==是不是就ok 了,对吧?他就能够找到嘛。
- 那么就是最后一种方式啊,不建议大家呢去使用啊,不推荐大家去使用!
为什么呢?
原因呢就是:这个/lib/ 目录,或者呢是user/lib啊,这里有很多的这个文件啊,对吧?很多的这个文件哈,包括一些库文件啊
呃不建议把我们自己的这个动态库文件放到这两个目录下的原因呢。就是他们本身里面就包含了一些系统自带的这个库文件,对吧?
那如果说你自己写的这个动态库文件和这个系统它的库文件名称一样了,对吧?
那你发现去的时候是不是有可能会把这个系统自带的这个库文件给他替换掉,是吧?
那可能啊我这个系统啊,他这个原先的动态库呢就不存在了,那么程序在运行的时候呢,可能就会出现一些问题啊,就是系统里的一些程序,它在运行的时候呢就可能会出现一些问题。
啊,所以说呢啊为了防止啊这个我们误操作啊
不建议的。大家呢啊把这个动态库放到这两个目录项啊
- 那么至于一个是环境变量配置,一个是这个啊
/etc/ld.so.cache
这个文件配置啊,这两种方式呢用哪一种都是ok 的啊。
# 6.静态库和动态库的对比✔️
- 如果使用到了『静态库』或『动态库』,他们都是在链接阶段进行的处理:
- 静态库:在程序链接阶段,会复制到可执行文件中
- 动态库:不一样,它在链接的时候,它并不会把动态库中的代码打包到可执行文件当中,而是打包一些动态库的一些信息:比如说动态库的一个名称,将来我这个可执行程序运行的时候,它需要干嘛?它需要找到你这个动态库的一个文件,然后把动态库加载到内存当中,然后才能去使用动态库当中的代码。
上面,是他们在链接阶段的区别,其实他们的区别就是来自『链接』阶段是如何去处理的。
==注意了这个静态库和动态库他们都是在链接阶段啊做的处理。==
- 动态库的制作过程
# 6.1.
我们先来提出一个问题,就是说这个动态库和静态库啊我们什么时候去使用动态库? 什么时候去使用静态库呢?
那么在这儿呢有一个这个规则啊,就是说一般情况下呢,如果说你的这个库啊非常小的话啊,那建议大家呢使用这个静态库啊
那如果说你这个啊库比较大的话啊,那建议进来大家使用动态库啊。
那为什么呢? 我们去看一看这个静态库的优缺点和动态库的优缺点之后啊,我们就能够明白了。
那么静态库的这个优点呢,一个呢就是静态库被打包到应用程序当中啊啊所以说它的这个加载速度呢要比这个啊动态库呢要快。 啊我们说这个静态库在程序链接的时候是不是就已经打包到应用程序当中了,对吧? 可执行程序当中了啊所以呢它的这个加载速度啊相对于动态库来说的话是要快一些的,那这个呢不用说啊。
移植性
- 发布程序的时候呢,我们无需提供静态库,移植方便什么意思啊?
那比如说呢我们啊用这个别人的静态库啊开发了一个程序,对吧?然后呢我们。
最后呢编译啊生成这个可执行文件啊,那么我们再去呃把这个可进行文件啊给别人去使用的时候啊,发布的时候,对吧?或者说移植的时候啊,给别人去使用的时候,那别人是不是直接拿到你这个可执行程序直接运行,是不就可以运行起来了?对吧?
我们来看一下一个图啊,我们看这个图呢就能够明白啊他的这个缺点。 比如说呢我现在呢有两个应用程序啊程序1和程序2啊。 那么呃程序1呢比如说。Program1.o这是它本身的这个呃程序生成的这个点欧文件,然后里面呢他用到了一个叫StaticMath.o这样的一个静态库啊,这是他用到了一个静态库。
那同样的这个程序要呢也是类似的啊,它里面呢也用到了这个静态库啊,static max这个静态库。 那么当这两个程序啊运行的时候呢,是不是要加载到内存当中啊? 对吧?那么我程序一加载进来的话,是不是要把这些代码都加载进来,对不对? 那我程序二啊它运行的时候是不是也要把这些内容啊这些代码加载到我们这个内存当中啊,对不对? 我们说静态库呢它会在链接的时候呢诶直接呢就打包到我们程序当中了,是不是啊? 那大家现在来看一下,你会发现在我们这个内存当中啊,是不是这个静态库的代码,它所占的内存是不是就有两份了? 所以说消耗系统资源,浪费内存吧,是不是啊?
那如果说你一个静态库啊,它所占用的内存呢是呃比如说是呃1M吧,对吧? 那你有一百个这样的程序都用到了这个静态库,那是不就占100M的这个内存啊,是吧? 那它浪费的这个内存呢是非常多的啊,消耗系统的资源。
然后呢我们再来看一下动态库的优缺点啊。 那么动态库的优点呢,第一个就是可以实现进程间资源共享。
啊那我们说了这个动态库呢它在我们内存当中啊,
它是动态加载的啊当我们程序啊使用到了动态库里面的内容了以后,那么他会把这个动态。 库它里面的代码呢加载到我们内存当中啊。
那比如说别的这个应用程序啊也用到了我这个动态库里面的内容了,那还需不需要再加载一份这个动态库的代码呢?是不需要的,他们可以共享,对吧?因为我们是不是配置了这个啊动态库它的一个路径啊,对吧? 它已经加载到内存里面了。啊所以说每一个这个应用程序啊它只要使用使用到了这个相同的动态库啊,只要这个动态库加载进去以后,就不会再加载了啊所以说呢可以实现这个进程间资源共享,所以说呢我们也称之为共享库。
- 然后呢还可以控制==何时加载==这个动态库。
啊其实就是我们这个动态库呢啊在我们使用到的时候呢,它才会动态地去加载,如果说你没有使通道的话呢它是不会去加载的,这是呃它的优点。
那么缺点呢就是呃一个是加载的速度呢相对于静态库呢比较慢啊因为我们都知道这个动态库呢它是动态去加载的,对吧?只有我们使用到了它才会去加载啊但是呢这个加载的速度呢呃其实相差呢不是特别多啊,只是说比这个静态库呢啊稍慢。
比如说呢这儿呢有两个程序啊程序一和程序二啊,里面呢都用到了这个动态库打navigation,so。 啊那注意了这个里面啊程序里面啊这个动态库啊,这个呃其实不是真正的把我们这个动态库的一个啊代码呢打包进来啊,只是呢简单地记录我们这个动态库的一些信息啊,
比如说记录的是这个动态库的名字,对吧?
那么下面这个绿色的才是我们真正的一个啊动态库的一个库文件啊。
那么程序运行的时候呢程序运行对吧?它会把这个程序运行起来。那么当它使用到这个动态库里面的API的时候,它会把这个动态库啊再加载到内存里面。
对了。是不是动态的去加载了呀?是吧?
它的这个位置呢是啊不确定的。
好,而这个静态库它的这个代码呢哎加载进来的时候呢其实是确定的啊,因为这个动态库它是动态加载的啊,加载进来的时候呢其实是确定的啊因为这个动态库它是动态加载的啊,所以说呢我们在生成这个动态库的时候呢,要加上这个-fpic
对吧?生成与这个位置无关的这个代码。
你想想,如果说你的动态库啊内容发生改变了,更新了,对吧?那直接呢把这个动态库重新生成一下,发送给别人,别人把这个动态库一替换啊,那你的程序。啊好,那么缺点呢就是这个呃加载的速度呢要比静态库慢一点是吧?
然后呢发布程序的时候呢也需要提供这个依赖的动态库的一个文件啊,库文件要提供给别人。
# 7.「编译器工程师」分析gcc源码
gcc -fdump-tree-all test.c
gcc -fdump-rtl-all test.c #生成rtl这种中间语言各个pass
gcc -fdump-rtl-peephole2 test.c
2
3