渝中网站建设,wordpress用户10亿,中山建设局网站,三星网站建设内容1.库的理解库就是写好的现有的#xff0c;成熟的#xff0c;可复用的代码。现实中每个程序都要依赖很多基础的底层库#xff0c;不可能每个人的代码都从零开始#xff0c;因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式#xff0c;是预编译代码的集…1.库的理解库就是写好的现有的成熟的可复用的代码。现实中每个程序都要依赖很多基础的底层库不可能每个人的代码都从零开始因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式是预编译代码的集合可以被程序重新使用,能够被操作系统载入内存执行。库有两种静态库.a、.lib和动态库.so、.dll。所谓静态、动态是指链接。具体来说二者链接的时间点不同代码被载入的时刻不同具体详见下文。回顾一下将一个程序编译成可执行程序的步骤2.源文件、编译、链接到可执行文件源代码要经过上图中预编译Processing、编译Compilation、汇编Assembly、链接Linking等步骤生成可执行文件。2.1编译(1)预编译即预处理主要处理在源代码文件中以“#”开始的预编译指令如宏展开、处理条件编译指令、处理#include指令等。(2)编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。(3)汇编是将汇编代码转变成机器可以执行的指令。至此C/C源代码文件经过预编译、编译和汇编直接输出目标文件.o文件)。这个过程也就是编译器所做的事即将高级语言翻译成机器语言比如我们用C/C语言写的一个程序可以使用编译器将其翻译成机器可以执行的指令及数据。现代的编译器将一个源代码文件编译成一个未链接的目标文件然后由链接器最终将这些目标文件链接起来形成可执行文件。2.2链接程序设计的模块化是人们一直在追求的目标,因为当一个系统十分复杂的时候,我们不得不将一个复杂的系统逐步分割成多个小的系统以达到各个突破的目的。一个复杂的软件也是如此,人们把每个源代码模块独立地编译,然后按照需要将它们“组装”起来,这个组装模块的过程就是链接(Linking),链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。3.静态链接与动态链接3.1静态链接最基本的静态链接过程如下图所示。每个模块的源代码文件(如.c)文件经过编译器编译成目标文件(Objet File,一般扩展名为.o或.obj), 目标文件和库(Library)一起链接形成最终可执行文件。而最常见的库就是运行时库(Runtime Library),它是支持程序运行的基本函数的集合。库其实是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。现代的编译和链接过程也并非想象中的那么复杂,它还是一个比较容易理解的概念。比如我们在程序模块main.c中使用另外一个模块func.c中的函数foo()。我们在main.c模块中每一处调用foo的时候都必须确切知道foo这个函数的地址,但是由于每个模块都是单独编译的,在编译器编译main.c的时候它并不知道foo函数的地址,所以它暂时把这些调用foo的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址修正。如果没有链接器,须要我们手工把每个调用foo的指令进行修正,然后填入正确的foo函数地址。当func.c模块被重新编译, foo函数的地址有可能改变时,那么我们在main.c中所有使用到foo的地址的指令将要全部重新调整。这些繁琐的工作将成为程序员的噩梦。使用链接器,你可以直接引用其他模块的函数和全局变量而无须知道它们的地址,因为链接器在链接的时候,会根据你所引用的符号foo, 白动去相应的func.c模块查找foo的地址,然后将main.c模块中所有引用到foo的指令重新修正,让它们的目标地址为真正的foo函数的地址。这就是静态链接的最基本的过程和作用。3.2动态链接静态链接这种方法的确很简单,原理上很容易理解,实践上很难实现,在操作系统和硬件不发达的早期,绝大部分系统采用这种方案。随着计算机软件的发展,这种方法的缺点很快就暴露出来了,那就是静态连接的方式对于计算机内存和磁盘的空间浪费非常严重。特别是多进程操作系统情况下,静态链接极大地浪费了内存空间。想象一下每个程序内部除了都保留着printf()函数、scanf()函数、strlen()等这样的公用库函数,还有数量相当可观的其他库函数及它们所需要的辅助数据结构。此外静态链接对程序的更新、部署和发布也会带来很多麻烦即一旦程序中有任何模块更新,整个程序就要重新链接、发布给用户。比如一个程序有20个模块,每个模块1 MB,那么每次更新任何一个模块,用户就得重新获取这个20 MB的程序。如果程序都使用静态链接,那么通过网络来更新程序将会非常不便,因为一旦程序任何位置的一个小改动,都会导致整个程序重新下载。要解决空间浪费和更新困难这两个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态地链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到运行时再进行,这就是**动态链接(Dynamic Linking)**的基本思想。同样举个小例子还是以Program1和Program2为例,假设我们保留Programl.o、Program2.o和Lib.o三个目标文件。当我们要运行Program1这个程序时,系统首先加载Program1.o,当系统发现Program1.o中用到了Lib.o,即Program1.o依赖于Lib.o,那么系统接着加载Lib.o,如果Program1.0或Lib.o还依赖于其他目标文件,系统会按照这种方法将它们全部加载至内存。所有需要的目标文件加载完毕之后,如果依赖关系满足,即所有依赖的目标文件都存在于磁盘,系统开始进行链接工作。这个链接工作的原理与静态链接非常相似,包括符号解析、地址重定位等。完成这些步骤之后,系统开始把控制权交给Program1.o的程序入口处,程序开始运行。这时如果我们需要运行Program2,那么系统只需要加载Program2.o,而不需要重新加载Lib.o,因为内存中已经存在了一份Lib.o的副本,系统要做的只是将Program2.0和Lib.o链接起来不同于静态链接还需要再次加载Lib.o的副本。4.静态库与动态库对比结合上述静态链接与动态链接的对比可以得到下面静态库与动态库的比较***静态库包含在编译时链接到用户程序的代码函数和数据都被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下编译链接生成可执行文件时链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.exe文件)。生成的可执行文件保留了自己的库代码副本。***动态库或共享库包含旨在由多个程序共享的代码由此动态库也称为共享库。库中的内容在运行时加载到内存中。每个可执行文件不维护其库的复制。使用它的时候往往提供两个文件一个引入库和一个DLL。引入库包含被DLL导出的函数和变量的符号名DLL包含实际的函数和数据。在编译链接可执行文件时只需要链接引入库DLL中的函数代码和数据并不复制到可执行文件中在运行的时候再去加载DLL访问DLL中导出的函数。这是使用静态库与动态库的说明。由下图可以看到静态库包含在可执行文件中。而动态库只需要在程序中创建一个符号表库代码中引用的函数、变量。在运行时动态库在现代操作系统中只被加载到内存中一次并在依赖它的所有程序之间共享。相反当使用静态库时每个可执行文件都必须将库代码加载到内存中。当有多个可执行文件运行时前者可以提高内存利用率。下图可以解释这种比较。使用静态库会导致两个明显的缺点1. 增加应用程序的大小。如果应用程序包含多个可执行文件问题会变得更糟。您最终可能会保留同一个库的多个副本。2. 修改/升级库代码需要重新运行应用程序其他部分的编译/链接。这可能是部署/维护目的的痛苦。大多数时候一个非接口相关的动态库升级不需要重新编译其他部分。通常由于上述原因人们多数时候倾向于选择动态库而不是静态库。然而动态库并不完美。他们对开发人员有自己的障碍——需要额外关注安装。与生成整体包的静态库不同动态库必须位于适当的位置以确保可执行文件可以在运行时找到库。-----静态库在程序编译时会被连接到目标代码中程序运行时将不再需要该静态库因此体积较大。-----动态库在程序编译时并不会被连接到目标代码中而是在程序运行是才被载入因此在程序运行时还需要动态库存在因此代码体积较小。动态库的好处是不同的应用程序如果调用相同的库那么在内存里只需要有一份该共享库的实例。带来好处的同时也会有问题如经典的DLL Hell问题。静态和动态 C 库之间的差异10条序号比较维度动态库静态库1build程序编译链接编译时不需要链接时需要编译时需要链接时不需要链接发生在构建使用静态库的客户端可执行文件时2二进制文件的本质没有启动例程的可执行文件。包含已解析的引用。目标文件的存档。所有部分都存在但大多数引用未解决本地引用除外3生成可执行文件后需要吗需要 动态库需要与可执行文件一起打包并且必须在可执行文件开始运行时调用更具体地说调用动态库提供的函数不需要仅在构建可执行文件在链接阶段才需要静态库。在运行可执行文件时不再需要因为库代码已嵌入在应用程序中。 4磁盘空间效率磁盘上应用程序之间的代码共享共享度高同一个动态库可以在磁盘上的多个可执行文件之间共享。共享度低每个可执行文件都需要链接它的静态库的单独副本。这会导致磁盘上出现大量二进制文件副本尤其是资源受限的移动设备。但是如果每个应用程序只使用整个静态库的一小部分磁盘空间效率仍然可以与单个大型 DLL 竞争。5内存效率高许多现代操作系统会尝试将动态库代码一次加载到内存中并在所有需要它的应用程序之间共享。例如一个 http 网络堆栈可能会在你的日历和笔记本应用程序之间共享。低如果 http 网络堆栈位于静态库中则每个需要此功能的应用程序都将加载它自己的网络堆栈副本并通常会影响运行时内存。6版本控制问题可能存在当应用程序使用的动态库版本与操作系统上存在的旧/新版本库冲突时你可能会遇到问题。 不存在由于所有库功能都链接到应用程序中因此系统上的其他应用程序是否使用不同版本的静态库并没有关系。7提供更新/补丁方便如果用户希望使用动态库的新ABI 兼容版本对应用程序打补丁他们只需从你那里获取一个新的 dll 并仅修补该 DLL而无需build整个应用程序。不太方便整个应用程序将需要重新构建和修补。这对大型应用程序来说是个大问题因为现在你需要通过网络提供更大的完整的更新文件。8控制加载是在某些系统中应用程序通过系统调用如Windows 上的 LoadLibrary明确控制何时加载和卸载库功能。这有助于在资源受限的系统上以有效的方式管理应用程序的内存。不是当应用程序启动时整个库被加载到进程空间并一直保留到应用程序关闭。9打包复杂在大多数系统中需要一个单独的步骤来为应用程序创建清单/依赖清单并将其打包。简单默认情况下与应用程序/可执行文件本身一起分发 - 无需单独打包。10开发过程中的适用性好只有动态库中的功能需要重新编译。繁琐整个应用程序将需要重新编译。对于大型应用工具或像 Office 这样大的应用程序如果所有功能都静态链接而不是在单独的 DLL 中则可能需要数小时。5.静态库与动态库的创建与使用5.1 创建和使用静态库在这个例子中我们将创建一个具有一个倒数函数的lib库。库源包含头文件 my_math.h 和源文件 my_math.cpp
#ifndef H_MY_MATH
#define H_MY_MATH// my_math.hdouble reciprocal(double d);#endif // H_MY_MATH#include my_math.h
//my_math.cppdouble reciprocal(double d) {return 1.0 / d;
}#include my_math.h
#include iostream
// main.cppint main(){std::cout reciprocal(2.0) std::endl;return 0;
}头文件 my_math.h 包含在 main.cpp 中它从库中调用函数在第一次编译中我们将 my_math.cpp 视为一个普通的源文件一切都按预期的那样g -c main.cpp -o main.o
g -c my_math.cpp -o my_math.o
g main.o my_math.o -o a.out
./a.out
0.5现在让我们将 my_math 打包为静态库。该过程包括 2 个步骤。第 1 步是使用上面相同的命令生成目标文件 my_math.o。第 2 步涉及使用arLinux ar chive 实用工具创建库文件ar cr libmy_math.a my_math.o“cr”标志表示创建一个新的静态库文件。它后面首先是输出文件名的请求名称。注意输出的名称是“libmy_math.a”。在 Linux 中将文件命名为 libXXX.a 作为静态库是一种惯例请务必这样做。当使用该库时命令行工具实际上依赖于此约定以使链接器正常工作。现在我们要使用静态库文件。一种方法是在 g/gcc 链接命令中将该文件与其他目标文件放在一起。g main.o libmy_math.a -o a.out另一种更常用的方法是使用 (-L) 和库名称 (-l) 显式指定库路径g main.o -L. -l my_math -o a.out这告诉编译器在路径 (.) 中查找名称为 libmy_math.a 的库。注意这里我们使用 -l my_math。链接器会将其视为指定文件名 libmy_math.a记住我们刚才谈到的创建库的命名约定。我们可以通过删除库并运行来验证库是否已被复制到可执行文件中rm libmy_math.a
./a.out
0.5的确有用。我们刚刚创建了一个静态库并在我们的程序中使用它验证了静态库的打包及使用。5.2 创建和使用动态库这次我们使用相同的示例代码而不是创建一个动态库。这是执行命令g -shared -o libmy_math.so my_math.o“-shared”标志指示生成共享库。同样输出文件命名约定 libXXX.so 是必须的稍后将由链接器使用。类似于静态库我们有两种方式来使用它。1. 将其作为链接器/编译器的输入。2.明确指定库位置-L和名称-lg main.o my_math.so -o a.out
# or
g main.o -L. -lmy_math -o a.out简单吧让我们运行它./a.out
./a.out: error while loading shared libraries: libmy_math.so: cannot open shared object file: No such file or directory糟糕的是我们遇到了一个错误生成了一个可执行文件。运行时试图找到一个名为 libmy_math.so 的共享库但是提示找不到。到底发生了什么以及如何解决呢事实证明用户必须向运行时的可执行文件或操作系统提供提示才能找到共享库 (libmy_math.so)。在 Linux 中有两种方式将共享库路径附加到环境变量 LD_LIBRARY_PATH。在构建可执行文件时使用-rpath标志指定共享库路径。我们再次测试一下将库路径添加到 LD_LIBRARY_PATHexport LD_LIBRARY_PATH$LD_LIBRARY_PATH:/home/cpp_tutorial/static_library
./a.out
0.5在运行时OS 会搜索 LD_LIBRARY_PATH 中的每个路径以“:”分隔以找到它需要的动态库。通过将路径附加到 LD_LIBRARY_PATH我们修复了它。2. 使用-rpath标志g main.o -L. -lmy_math -o a.out -Wl,-rpath,/home/cpp_tutorial/static_library
./a.out
0.5这里的“-Wl 标志”意味着它后面的内容以逗号分隔的标志列表将被传递给链接器。在这种情况下“-rpath /home/cpp_tutorial/static_library”被传递给链接器。链接器将此路径信息插入到可执行文件 (a.out) 自己的搜索路径中。这也有效。比较这两种方法修改 LD_LIBRARY_PATH 涉及更改影响所有程序的全局变量。使用-rpath通常是首选方式因为它是本地更改不会改变其他可执行文件的行为参考资料https://blog.csdn.net/qq_41073715/article/details/118516662https://www.acodersjourney.com/cplusplus-static-vs-dynamic-libraries/https://domiyanyue.medium.com/c-development-tutorial-4-static-and-dynamic-libraries-7b537656163e