金坛建设网站,物流公司响应式网站建设,wordpress如何设置404,联盟网站做任务目录
十三.动态库与静态库 13.1 认识动静态库 13.2 深入理解动静态库 什么是库? 编译链接过程 动静态库的基本原理 13.3 静态库 静态库的打包: 静态库的使用: 13.4 动态库 动态库的打包: 动态库的使用: 13.5 动态库与静态库怎么选? 十三.动态库与静态库 13.1 认识动静态库 …目录
十三.动态库与静态库 13.1 认识动静态库 13.2 深入理解动静态库 什么是库? 编译链接过程 动静态库的基本原理 13.3 静态库 静态库的打包: 静态库的使用: 13.4 动态库 动态库的打包: 动态库的使用: 13.5 动态库与静态库怎么选? 十三.动态库与静态库 13.1 认识动静态库 静态库.a程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库动态库.so程序在运行的时候才去链接动态库的代码多个程序共享使用库的代码。 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表而不是外部函数所在目标文 件的整个机器码在可执行文件开始运行以前外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中这个过程称为动态链接dynamic linking动态库可以在多个程序间共享所以动态链接使得可执行文件更小节省了磁盘空间。操作系统采用虚 拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用节省了内存和磁盘空间 在Linux当中以.so为后缀的是动态库以.a为后缀的是静态库。在Windows当中以.dll为后缀的是动态库以.lib为后缀的是静态库。
ldd命令可以查看一个可执行程序依赖的共享库
ldd 可执行程序名gcc/g编译器默认都是动态链接的若想进行静态链接可以携带 -static 选项:
# 动态链接
gcc -o my_program my_program.c # 静态链接
gcc -o my_program_static my_program.c -static13.2 深入理解动静态库
什么是库?
在计算机编程中库Library是一组已经编写好的代码、函数和例程的集合可以供程序员在自己的程序中重复使用。这些库中的代码通常提供了一些通用的功能例如数据结构、算法、输入/输出操作等以便开发人员能够更容易地实现特定的任务而不必从头开始编写所有的代码
编译链接过程
我们都知道C/C程序从源代码到可执行程序会经历下面四个步骤: 预处理 (Preprocessing): 在这一阶段预处理器会对源文件进行处理展开头文件#include指令执行宏替换去除注释处理条件编译#if、#ifdef等生成一个被称为预处理后文件通常具有.i扩展名。编译 (Compiling): 编译器接收预处理后的文件进行词法分析、语法分析和语义分析生成相应的汇编代码。这个阶段的输出是一个汇编文件通常具有.s扩展名。汇编 (Assembling): 汇编器接收编译生成的汇编代码将其转换为机器语言二进制代码并生成目标文件通常是.o文件。目标文件包含与源文件对应的机器代码。链接 (Linking): 链接器接收一个或多个目标文件以及可能的库文件将它们组合在一起生成最终的可执行文件。链接的过程包括符号解析、地址绑定和重定位等步骤最终生成可执行文件。 动静态库的基本原理
而动静态库的区别其实主要体现在程序链接 (Linking)阶段
静态库的链接阶段 完全合并到可执行文件 在链接阶段静态库的目标文件会被完全合并到最终的可执行文件中。 可执行文件独立 最终生成的可执行文件包含了程序的所有代码和数据独立于外部库文件。 链接时解析符号 编译器在链接时会将程序中引用的符号与静态库中的符号进行解析和匹配将所有符号解析为具体的地址。 生成独立的可执行文件 链接阶段生成的可执行文件是一个独立的二进制文件不需要依赖外部库文件。 动态库的链接阶段 引用和延迟加载 在链接阶段编译器并不将动态库的目标文件合并到可执行文件中。相反它在可执行文件中留下对动态库的引用。 生成未定义符号 编译器生成一个包含未定义符号引用的可执行文件这些符号在运行时将由动态链接器解析。 符号解析延迟到运行时 实际的链接和加载是在程序运行时由操作系统的动态链接器完成的。动态链接器负责加载和链接动态库。 生成依赖于动态库的可执行文件 可执行文件包含有关如何在运行时加载和链接动态库的信息但并不包含动态库的实际代码。 总体而言静态库在链接阶段就将库的代码完全合并到可执行文件中而动态库的链接是延迟到运行时进行的。这是为什么动态库能够实现共享和动态加载的原因。 光说不练假把式,下面我们在实操中进一步体会! 13.3 静态库
首先创建一个项目目录例如 calculator_project在其中创建以下文件:
创建文件结构
calculator_project/
|-- src/
| |-- add.c
| |-- multiply.c
| |-- main.c
|-- include/
| |-- calculator.h
|-- lib/静态库的打包:
编写源文件:
编写 add.c
// add.c
int add(int a, int b) {return a b;
}编写 multiply.c
// multiply.c
int multiply(int a, int b) {return a * b;
}编写头文件
编写 calculator.h
// calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_Hint add(int a, int b);
int multiply(int a, int b);#endif编译源文件 :
这时候回到我们的回到calculator_project目录,使用以下命令编译源文件生成目标文件
gcc -c src/add.c -o lib/add.o
gcc -c src/multiply.c -o lib/multiply.o
这将分别编译 add.c、multiply.c 并将生成的目标文件保存在 lib 目录中
创建静态库
我们进入lib目录,使用以下命令创建静态库 libcalculator.a
ar -rcs libcalculator.a add.o multiply.oar 是GNU下一个用于创建和操作静态库archive的工具,而 rcs 是 ar 工具的一组选项用于创建、替换或显示归档文件。 具体而言ar rcs 命令的含义是 -rreplace用于将指定文件插入到归档文件中。如果文件已经存在于归档中则替换掉原有的文件。-c (create) 创建归档文件。如果归档文件不存在则创建一个新的归档文件。-s (index symbol table) 创建索引。创建归档文件的索引这样可以更快地查找文件。 综合起来ar rcs 命令通常用于创建或更新静态库的归档文件将新的目标文件插入到归档中并为归档文件创建索引。 静态库的使用: 编写 main.c
// main.c
#include stdio.h
#include calculator.hint main() {int result_add add(5, 3);int result_multiply multiply(4, 2);printf(Addition result: %d\n, result_add);printf(Multiplication result: %d\n, result_multiply);return 0;
}编译源文件
在src目录,使用以下命令编译源文件生成目标文件
gcc -c main.c -o ../lib/main.o -I ../include编译可执行文件
使用以下命令编译可执行文件
gcc lib/main.o -o calculator -L lib -lcalculator此时使用gcc编译main.c生成可执行程序时需要携带三个选项 -I指定头文件搜索路径。-L指定库文件搜索路径。-l指明需要链接库文件路径下的哪一个库。 运行程序
运行生成的可执行文件 13.4 动态库
关于动态库,我们会基于静态库的实现来进一步来讲解
动态库的打包:
编译源文件 :
这时候回到我们的回到calculator_project目录,使用以下命令编译源文件生成目标文件
gcc -c -fPIC src/add.c -o lib/add.o
gcc -c -fPIC src/multiply.c -o lib/multiply.o
-fpic 是 GCC 编译器的一个选项用于生成位置无关代码Position Independent CodePIC。位置无关代码是一种编译生成的代码可以在内存中的任何位置执行而无需修改。这对于动态链接库(共享库)非常重要因为共享库可以加载到内存的任何位置。
具体来说-fpic 选项的作用包括 生成相对地址 使用 -fpic 选项编译生成的代码中引用地址都是相对地址而不是绝对地址。这使得代码可以在不同的内存位置正确执行。用于共享库 这个选项通常用于编译动态库确保生成的库是位置无关的。 如果在创建动态库时不加 -fPIC 选项会导致生成的目标文件包含绝对地址的引用。这样的目标文件是非位置无关的即在加载时必须进行重定位以适应不同的内存地址 每个进程都需要独立的重定位 因为库中的代码包含绝对地址的引用加载到内存的每个进程都需要对代码段进行独立的重定位以适应不同的内存地址。这意味着每个进程加载库时都需要修改代码段的内容以反映加载到的特定地址从而创建独立的、可重定位的拷贝。 无法在多个进程之间共享 由于每个进程都需要对代码段进行独立的重定位这导致了每个进程都会维护一个独立的、特定于该进程的库拷贝。因此这样的库不能在多个进程之间共享因为每个拷贝都是特定于加载它的进程的。 内存占用可能更大 每个进程都拥有自己的独立拷贝这可能导致内存占用更大。相比之下位置无关的库可以在多个进程之间共享从而减少内存占用。 创建动态库
我们进入lib目录,使用以下命令创建静态库 libcalculator.so
gcc -shared -o lib/libcalculator.so lib/add.o lib/multiply.o
与生成静态库不同的是生成动态库时我们不必使用ar命令我们只需使用gcc的-shared选项即可。 动态库的使用:
编译可执行文件
使用以下命令编译可执行文件
gcc lib/main.o -o calculator_dynamic -L lib -lcalculator
运行程序
运行生成的可执行文件 这时候我们会惊讶的发现动态链接生成的程序不能像静态链接一样直接运行 这时候我们看一下系统的报错,发现提示我们找不到链接的libcalculator.so库!
可是我们使用-I-L-l这三个选项都是在编译期间已经告诉过编译器我们使用的头文件和库文件在哪里以及是谁,那么在执行的时候是如何定位动态库文件的呢 这时候我们使用ldd命令来查看程序的动态链接依赖关系时,我们发现c语言的动态库都能找到所在的位置,而我们自己编写的动态库却not found.
原来,系统中存在环境变量LD_LIBRARY_PATH 用于指定动态链接器在运行时查找共享库的路径。当你运行一个程序时系统的动态链接器会根据该变量的设置来搜索共享库的位置。 基于此我们可以有一下几种方法来使我们的程序正常执行 方法一直接更改LD_LIBRARY_PATH 我们只需将动态库所在的目录路径添加到LD_LIBRARY_PATH环境变量当中即可。
我们先通过pwd命令得到我们自己lib目录的绝对地址: 这时候我们再将我们得到的库的地址(记作path)添加到LD_LIBRARY_PATH环境变量当中
export LD_LIBRARY_PATH path:$LD_LIBRARY_PATH这里我加上自己库的path后运行程序 方法二将目录拷贝到系统的动态库目录下 同上,我们先通过pwd命令得到我们自己lib目录的绝对地址: 将目录中的所有共享库文件拷贝到 /usr/lib这是系统默认的动态库目录之一。你可能需要使用 sudo 权限来执行这个命令
sudo cp path/*so /usr/lib不推荐将自己写的库文件拷贝到系统路径下这样做会对系统文件造成污染,该方法仅做演示 方法三配置/etc/ld.so.conf.d/ 我们可以通过配置/etc/ld.so.conf.d/的方式解决该问题/etc/ld.so.conf.d/路径下存放的全部都是以.conf为后缀的配置文件而这些配置文件当中存放的都是路径系统会自动在/etc/ld.so.conf.d/路径下找所有配置文件里面的路径之后就会在每个路径下查找你所需要的库。我们若是将自己库文件的路径也放到该路径下那么当可执行程序运行时系统就能够找到我们的库文件了。 /etc/ld.so.conf.d/ 目录下的 .conf 文件来管理库文件的路径是一种更规范的方式。这样做可以使系统更清晰地了解到库文件的位置并且不直接修改系统默认库目录减小了对系统的影响。
创建一个 .conf 文件
在你的库文件所在目录创建一个以 .conf 为后缀的配置文件比如my_library.conf,并将库文件所在目录的路径存入 将库文件所在的路径写入配置文件中:
将 .conf 文件拷贝到 /etc/ld.so.conf.d/ 目录下
sudo cp my_library.conf /etc/ld.so.conf.d/
这样系统会自动读取该目录下的配置文件。
使用 ldconfig更新配置
sudo ldconfig 这个命令会重新加载配置文件确保系统动态链接器能够找到新的库文件路径。 此时系统应该能够正确找到你的库文件你的可执行文件也应该能够正常运行了。
这是一种更安全和规范的方式尤其在共享库比较多或者与其他应用程序存在依赖关系时非常有用。 13.5 动态库与静态库怎么选?
静态库的优势 独立性 静态库会将库的代码嵌入到可执行文件中使得程序在运行时不再依赖外部的库文件。这使得静态库的使用更为简单因为用户不需要担心库文件的版本问题。性能 静态库在编译时就已经链接到可执行文件中因此在运行时不需要进行额外的加载和链接操作有助于提高程序的启动速度。 动态库的优势 共享性 动态库可以被多个程序共享使用这有助于减小可执行文件的大小因为多个程序可以共同使用同一个动态库而不是每个程序都包含一份库的拷贝。更新维护 如果库需要升级或修复 bug只需更新动态库而无需重新编译所有依赖于它的程序。这降低了维护成本。节省内存 动态库在内存中只需要加载一次多个程序可以共享同一份库的实例因此可以减少内存的占用。 选择建议 静态库 适用于小型项目或独立的工具可以简化部署和分发避免依赖管理的复杂性。特别是对于一些简单的工具或嵌入式系统静态库可能更为合适。动态库 适用于大型项目特别是涉及到共享代码、更新频繁或需要动态加载的场景。对于框架和库的开发者使用动态库通常更为灵活。 最终的选择取决于项目的具体需求、开发团队的偏好以及目标平台的要求。在实际开发中有时候也会选择混合使用即某些库采用静态链接而另一些采用动态链接以充分发挥各自的优势。