欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【LeetCode】winter vacation training
🌈静态库概念
当编译和链接程序时,代码和函数通常被组织成不同的库,其中一种常见的库类型是静态库(Static Library)。静态库是一组预编译的对象文件的集合,它们被打包在一个单独的文件中,以供程序在编译时静态链接使用。
静态库包含了编译过的目标代码,可以被多个程序共享使用。它的主要特点是在编译时将库的代码和数据复制到可执行文件中,使得可执行文件独立于系统上是否存在该库的副本。这意味着在使用静态库时,程序不需要依赖外部的共享库文件,因为所有必要的代码都已经被提前包含在了可执行文件中。
使用静态库的优点包括:
然而,静态库也存在一些限制和缺点:
总结来说,静态库是一种包含预编译目标代码的库文件,它在编译时被静态链接到可执行文件中。它提供了独立性和性能优势,但可能导致可执行文件增大并需要额外的维护工作。
🌈动态库概念
动态库(Dynamic Linking Library)是一种包含可重用代码和数据的库文件,它在程序运行时被动态加载到内存中,并与程序共享使用。与静态库不同,动态库的代码和数据不会在编译时被复制到可执行文件中,而是在运行时通过动态链接器进行加载和链接。
动态库具有以下几个关键特点:
动态加载:动态库在程序运行时才被加载到内存中,而不是在编译时被静态链接到可执行文件中。这意味着程序可以在运行时根据需要加载或卸载动态库,从而实现更灵活的模块化设计。
共享性:多个程序可以同时使用同一个动态库,这样可以节省系统资源并提高代码重用性。动态库的共享性使得不同的程序可以共享相同的函数、类、变量等,减少了重复编写和维护的工作量。
运行时依赖:使用动态库的程序依赖于系统中已安装的动态库文件。如果某个程序需要使用某个动态库,但系统中没有该库,那么程序将无法正常运行。因此,在部署程序之前,需要确保所依赖的动态库已经正确安装在目标系统上。
灵活性:由于动态库的代码和数据可以在运行时加载,因此可以实现动态更新和升级。如果需要更新动态库的版本,只需要替换库文件即可,而不需要重新编译整个程序。
性能优化:动态库的代码和数据可以被多个程序共享,这可以减少内存占用并提高执行效率。此外,动态库的加载是按需进行的,可以减少程序的启动时间,提高用户体验。
需要注意的是,动态库也有一些潜在的问题,如不同版本的动态库之间的兼容性、动态库的加载和链接时间等。因此,在使用动态库时需要注意版本管理和依赖管理,以确保程序的稳定性和可靠性。
总结来说,动态库是一种在程序运行时被动态加载到内存中的库文件,它具有共享性、灵活性和性能优化等特点。通过使用动态库,可以提高代码重用性、减少内存占用,并实现动态更新和升级。
ar
命令是Linux操作系统中的一个命令行工具,用于创建、修改和管理静态库。静态库是一组已经编译好的对象文件(.o文件)的集合,可以供其他程序链接使用。
ar
命令的基本语法格式如下:
ar [options] archive file...
其中,archive
表示要操作的静态库文件名,file...
表示要加入或删除的目标文件名列表。
ar
命令支持的常用选项包括:
r
:将目标文件添加到静态库中,如果静态库中已经存在同名文件,则替换;d
:从静态库中删除目标文件;t
:显示静态库中包含的文件列表;x
:从静态库中提取指定的文件;s
:在静态库中创建符号表。例如,要将两个目标文件 foo.o
和 bar.o
添加到名为 libfoo.a
的静态库中,可以使用以下命令:
ar rcs libfoo.a foo.o bar.o
其中,r
选项表示添加文件到静态库中,c
选项表示创建静态库(如果不存在的话),s
选项表示创建符号表。
要从静态库中删除一个目标文件 foo.o
,可以使用以下命令:
ar d libfoo.a foo.o
要在静态库中提取一个文件 foo.o
,可以使用以下命令:
ar x libfoo.a foo.o
注意:库真正的名字要去掉前面的lib和后缀.a
ldd
命令是 Linux 上的一个用于查看可执行文件或共享库文件所依赖的动态链接库的工具。它的全称是 “List Dynamic Dependencies”。
ldd
命令的基本语法如下:
ldd [options] <executable_file>
其中,executable_file
是要检查依赖库的可执行文件或共享库文件。
ldd
命令会列出指定可执行文件或共享库文件所依赖的动态链接库列表。对于每个依赖项,它会显示库文件的路径,以及该库在运行时是否可以找到。如果库文件路径前面有 =>
符号,表示该库文件被找到并加载了。
ldd
命令还支持一些选项,常用的选项包括:
-v
:显示详细的信息,包括版本和符号表等。-u
:显示未使用的直接依赖项。-r
:递归地检查所有依赖项的依赖关系。-d
:显示输出的依赖项的调试信息。-f
:对于符号链接,显示链接的文件名和符号链接目标的路径。例如,要查看可执行文件 myprogram
所依赖的动态链接库,可以使用以下命令:
ldd myprogram
ldd
命令会列出该可执行文件所依赖的所有动态链接库,并显示它们的路径和状态。
需要注意的是,ldd
命令只能查看动态链接库的依赖关系,对于静态链接库不起作用。另外,在某些情况下,ldd
命令无法正确识别某些依赖关系,因此在使用时需要注意其局限性。
我们自己写的第三方库对于编译器来说,编译器是不认识的
像c库这种的第一方库就在系统的默认静态库目录中,编译器默认从那里找
编译器会找不到我们第三方库中的文件,那么我们在gcc编译时该怎么做呢?这里我们复习一下gcc的命令选项
下面是 gcc
常用的一些命令选项:
-c
:只编译源文件,生成目标文件。-o <file>
:指定输出文件的名称。-Wall
:启用所有常见的警告信息。-Wextra
:启用额外的警告信息。-g
:生成调试信息,用于调试程序。-O
:优化代码,有不同的级别如 -O1
、-O2
、-O3
。-I <dir>
:指定头文件的搜索路径。-L <dir>
:指定库文件的搜索路径。-l <library>
:链接指定的库文件。-D <macro>
:定义一个宏。-E
:只进行预处理,生成预处理后的文件。-S
:只进行编译,生成汇编文件。-shared
:生成共享库文件。-static
:生成静态可执行文件。(必须静态链接,没有静态库直接报错)-pthread
:链接线程库。-std=<standard>
:指定使用的C语言标准,如 -std=c99
、-std=c11
。这里我们主要用到 -l <library>
:链接指定的库文件、-L <dir>
:指定库文件的搜索路径、 -I <dir>
:指定头文件的搜索路径。
如果我们要将自己写的库打包起来给别人,分为以下步骤:
1.gcc -c 目标.c文件
命令将目标.c文件变为.o目标文件
2.使用ar命令
将.o文件集合成静态库
3.至此我们要分别创建两个文件夹:lib文件夹和include文件夹,前者用来存放静态库.a文件,后者存放头文件.h文件
4.lib文件夹和include文件夹放在同一个文件目录底下,最后用tar czf
命令将文件压缩为zip文件,即打包完成
以下是一个示例:
🔥方法一:将第三方库导入到系统默认库中
将第三方库文件中的lib 文件和include文件分别复制导系统默认的lib文件和include文件中
🔥方法二:使用gcc选项L,-l,-I
如果已经操作过方法一了,则-大i和-L可以不用。只需要指明链接的库名称即可
示例:
[root@localhost linux]# gcc -fPIC -c sub.c add.c
[root@localhost linux]# gcc -shared -o libmymath.so*.o
[root@localhost linux]# ls add.c add.h add.o libmymath.so main.c sub.c sub.h sub.o
动态库的形成和使用与静态库其实都大差不差。
但是就是可执行程序时,系统会找不到动态库,因为动态链接时,系统只会在系统默认的lib和include文件下或者当前目录下进行查找(即使你告诉了编译器路径,但是此时已经跟编译器无关了,跟系统有关!!)。
🔥方法一:将第三方库导入到系统默认库中
将第三方库文件中的lib 文件和include文件分别复制导系统默认的lib文件和include文件中
🔥方法二:建立软链接
系统你不是会在当前目录下找吗,那我就在当前目录下建立一个软链接,这样在当前目录下就可以找到对应的头文件和动态库
🔥方法三:添加环境变量
LD_LIBRARY_PATH
是一个环境变量,用于告诉动态链接器(ld.so)在加载动态链接库时要搜索的路径。当程序启动时,动态链接器会按照一定的顺序来搜索动态链接库,其中包括 $LD_LIBRARY_PATH
环境变量指定的路径。
如果某个动态链接库在默认的路径中找不到,可以设置 LD_LIBRARY_PATH
环境变量来指定其所在路径。例如,假设我们编译了一个程序 myprogram
,它依赖于一个共享库 libmylibrary.so
,但该库不在标准路径中,我们可以通过以下命令来运行程序:
export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH
./myprogram
这样,当程序运行时,动态链接器会首先在 /path/to/library
目录中搜索共享库 libmylibrary.so
,如果找到则加载该库。
需要注意的是,LD_LIBRARY_PATH
环境变量只对当前 shell 有效,如果要使其对所有 shell 会话都有效,应该将其添加到 shell 的初始化文件中(如 ~/.bashrc
、/etc/profile
等)。另外,由于 LD_LIBRARY_PATH
可以被恶意用户滥用,因此在设置时需要谨慎,确保只添加必要的目录。
🔥方法四:直接更改系统关于动态库的配置文件
用sudo权限在/etc/ld.so.conf.d/
目录下创建一个.conf文件
,在文件中粘贴进去你动态库的位置路径即可。
如果没有生效可用ldconfig
命令刷新一下
同一组库中既提供了动态库又提供了静态库,gcc默认使用动态库
在 Linux 中,可执行文件和共享库都使用 ELF
(Executable and Linkable Format)格式。ELF 格式是一种与平台无关的二进制格式,它包含了程序的代码、数据和元信息等内容,并且可以在编译时和运行时进行链接。
ELF 文件由三个部分组成:头部
、节区表
和节区数据
。其中,头部包含了 ELF 文件的基本信息,如文件类型、目标机器体系结构、入口地址、程序头表和节区头表等;节区表则包含了各个节区的描述信息,如名字、偏移量、大小、属性等;节区数据则包含了实际的代码、数据和符号信息等。
对于可执行程序,ELF 文件中的代码和数据是直接可执行的,而符号表则包含了程序中定义和引用的所有符号,如函数名、全局变量名等。当程序被加载到内存中后,动态链接器会根据符号表中的信息来解析并加载依赖的共享库,并修复程序中的符号引用。最终,程序就能够正常运行了。
动态链接的程序,不光光自己要加载,链接的库也要加载
需要注意的是,不同的操作系统和体系结构可能使用不同的 ELF 格式版本,因此在跨平台或跨架构编译时需要进行相应的调整和处理。此外,由于 ELF 文件包含了大量的元信息,因此它的大小通常比较大,这也是一些嵌入式设备和资源受限系统不适合使用 ELF 格式的原因之一。
🌔一些拓展知识:
程序没有被加载,程序内部有地址吗?答:有的,但注意是虚拟地址(逻辑地址:基地址+偏移量)
变量名、函数名等,编译成为2进制,还有地址吗? 答:没有
编译的时候,对代码进行编址,基本遵守虚拟地址空间的那一套。虚拟地址空间,不仅仅时OS里面的概念,编译器编译的时候也按照这个规则进行编译可执行程序,这样才能在加载的时候,进行从磁盘文件到内存的映射
绝对编址和相对编址是计算机体系结构中用于指令和数据访问的两种不同方式。
绝对编址(Absolute Addressing):
在绝对编址中,指令或数据的地址是直接给出的物理地址。也就是说,每个内存单元都有一个唯一的物理地址,程序中使用的地址就是实际的物理地址。当程序执行时,处理器直接使用这些实际的物理地址来访问内存中的指令和数据。绝对编址通常用于固定的内存布局,例如裸机编程或实模式下的操作系统。
相对编址(Relative Addressing):
在相对编址中,指令或数据的地址是相对于某个参考点的偏移量。这个参考点可以是当前指令的地址、当前指令所在的段基址、或者其他指定的基址寄存器。当程序执行时,处理器将相对地址与基址相加来计算内存中的实际地址,然后再访问指令和数据。相对编址通常用于采用分段或分页机制的操作系统,可以提供更大的地址空间,并且可以灵活地将不连续的物理内存映射到连续的逻辑地址空间中。
绝对编址和相对编址各有其特点:
需要注意的是,不同的计算机体系结构和操作系统可能使用不同的地址编址方式,因此在开发和移植程序时需要考虑和适配相应的编址方式。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹?? 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长