静态库与动态库总结
一、库文件和头文件
所谓库文件,可以将其理解为压缩包文件,该文件内部通常包含不止一个目标文件(也就是二进制文件)。
值得一提的是,库文件中每个目标文件存储的代码,并非完整的程序,而是一个个实用的功能模块。例如,C 语言库文件提供有大量的函数(如 scanf()、printf()、strlen() 等),C++ 库文件不仅提供有使用的函数,还有大量事先设计好的类(如 string 字符串类)。
库文件的产生,极大的提高了程序员的开发效率,因为很多功能根本不需要从 0 开发,直接调取包含该功能的库文件即可。并且,库文件的调用方法也很简单,以 C 语言中的 printf() 输出函数为例,程序中只需引入 <stdio.h> 头文件,即可调用 printf() 函数。
头文件和库文件的关系:
头文件只存储变量、函数或者类等这些功能模块的声明部分;
库文件才负责存储各模块具体的实现部分;
所有的库文件都提供有相应的头文件作为调用它的接口。也就是说,库文件是无法直接使用的,只能通过头文件间接调用。
头文件和库文件相结合的访问机制,最大的好处在于,有时候我们只想让别人使用自己实现的功能,并不想公开实现功能的源码,就可以将其制作为库文件,这样用户获取到的是二进制文件,而头文件又只包含声明部分,这样就实现了“将源码隐藏起来”的目的,且不会影响用户使用。
事实上,库文件只是一个统称,代指的是一类压缩包,它们都包含有功能实用的目标文件。要知道,虽然库文件用于程序的链接阶段,但编译器提供有 2 种实现链接的方式,分别称为静态链接方式和动态链接方式,其中
采用静态链接方式实现链接操作的库文件,称为静态链接库;
采用动态链接方式实现链接操作的库文件,称为动态链接库;
在 C、C++ 实际开发过程中,除了可以使用系统库文件外,我们还可以根据实际需要,手动创建静态链接库或者动态链接库。
二、静态链接和动态链接
我们把编译后但是还未链接的二进制机器码文件称为目标文件(Object File),那些第三方库是其他人编译打包好的目标文件,这些库里面包含了一些函数,我们可以直接调用而不用自己动手写一遍。在编译构建自己的可执行文件时,使用静态链接的方式,其实就是将所需的静态库与目标文件打包到一起。最终的可执行文件除了有自己的程序外,还包含了这些第三方的静态库,可执行文件比较臃肿。相比而言,动态链接不将所有的第三方库都打包到最终的可执行文件上,而是只记录用到了哪些动态链接库,在运行时才将那些第三方库装载(Load)进来。装载是指将磁盘上的程序和数据加载到内存上。
静态库的本质其实就是 .o 文件的集合,所以它的基础表现与 .o文件没有区别——在链接时(link time),链接器从静态库中搜索所有的可见全局函数/变量符号,并且把这些符号复制到二进制文件(通常是可执行程序)中。
但需要注意的是,并非任何一个源文件都可以被加工成静态链接库,其至少需要满足以下 2 个条件:
1、源文件中只提供可以重复使用的代码,例如函数、设计好的类等,不能包含 main 主函数;
2、源文件在实现具备模块功能的同时,还要提供访问它的接口,也就是包含各个功能模块声明部分的头文件;
静态链接库实现链接操作的方式很简单,即程序文件中哪里用到了库文件中的功能模块,GCC 编译器就会将该模板代码直接复制到程序文件的适当位置,最终生成可执行文件。
使用静态库文件实现程序的链接操作,既有优势也有劣势:
优势是,生成的可执行文件不再需要任何静态库文件的支持就可以独立运行(可移植性强);
劣势是,如果程序文件中多次调用库中的同一功能模块,则该模块代码势必就会被复制多次,生成的可执行文件中会包含多段完全相同的代码,造成代码的冗余。
和使用动态链接库生成的可执行文件相比,静态链接库生成的可执行文件的体积更大。
相比之下,动态链接主要有以下好处:
多个可执行文件可以共享使用系统中的共享库。每个可执行文件都更小,占用的磁盘空间也相对比较小。而静态链接把所依赖的库打包进可执行文件,假如printf()被其他上千个程序使用,就要被打包到上千个可执行文件中,这样会占用了大量磁盘空间。
共享库的之间隔离决定了共享库可以进行小版本的代码升级,重新编译并部署到操作系统上,并不影响它被可执行文件调用。静态链接库的任何函数有了改动,除了静态链接库本身需要重新编译构建,依赖这个函数的所有可执行文件都需要重新编译构建一遍。
当然,共享库也有缺点:
如果将一份目标文件移植到一个新的操作系统上,而新的操作系统缺少相应的共享库,程序将无法运行,必须在操作系统上安装好相应的库才行。也即编译链接时使用的动态库可能是1.0版本,实际运行的操作系统上动态库可能只有2.0版本,此时程序就无法运行。
大多数现代操作系统(Linux、windows 等)都支持动态链接库,也即支持在程序运行时(runtime)链接动态库。动态链接库的文件名在 Linux 中通常以 .so 结尾,在 Windows 中则通常以 .dll 结尾。静态链接库的文件名在 Linux 中通常以 .a 结尾,在 Windows 中则通常以 .lib结尾。
静态链接:
编译器在编译可执行文件时,把需要用到的对应动态链接库(.so或.ilb)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行时不需要依赖于动态链接库。
静态lib将导出声明和实现都放在lib中。编译后所有代码都嵌入到宿主程序。
动态链接:
动态链接的可执行文件需要附带一个的动态链接库,在执行时,需要调用其对应动态链接库中的命令。所以其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。缺点一是哪怕是很简单的程序,只用到了链接库中的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。
三、创建和使用静态库
libtool和ar都是Linux系统用来创建和管理静态库的工具。通常使用ar创建静态库。
1、libtool
以下是使用 libtool 创建静态库的基本步骤:
-
编写源代码: 首先,你需要有一组源代码文件,例如
file1.c和file2.c。 -
编译源代码: 使用
libtool编译源代码文件。这通常涉及到将.c文件转换为.lo(libtool object)文件。libtool --mode=compile gcc -c file1.c -o file1.lo libtool --mode=compile gcc -c file2.c -o file2.lo这里,
--mode=compile告诉libtool进入编译模式,gcc是编译器,-c表示编译不链接,输出.lo文件。 -
创建静态库: 使用
libtool将.lo文件打包成一个静态库。例如,创建一个名为libmylib.a的库:libtool --mode=link gcc -o libmylib.a file1.lo file2.lo这里,
--mode=link告诉libtool进入链接模式,gcc是链接器,-o指定输出文件名。 -
安装库: 可选地,你可以将库安装到系统的标准库目录中:
libtool --mode=install cp libmylib.a /usr/local/lib这里,
--mode=install用于安装模式,cp是复制命令,将库复制到/usr/local/lib目录。 -
更新库索引: 如果你的系统中有多个
.la(libtool库辅助)文件,可以使用以下命令更新库索引:libtool --config然后按照提示操作。
请注意,libtool 通常与 autoconf 和 automake 结合使用,以自动处理库的创建和管理。如果你的项目使用了 autoconf 和 automake,那么你可能会在 Makefile.am 文件中定义库的创建规则,然后使用 make 命令来构建库。
在实际使用中,你可能需要根据你的具体需求和环境来调整命令和参数。如果你需要更详细的帮助,可以查看 libtool 的手册页(通过在终端运行 man libtool 命令)。
2、ar
r 是一个用于创建、修改和提取归档(archive)文件的命令行工具,它在 Unix-like 系统上广泛使用。归档文件通常用于将多个文件组合成一个单一的文件,以便于分发和存储。ar 操作的归档文件通常具有 .a 扩展名,表示它们是静态库文件。
以下是一些常用的 ar 指令及其选项:
-
-c (create) 创建归档文件: 这个选项用于创建一个新归档文件。如果归档文件已经存在,并且使用
c选项,ar会报错。如果想要创建新归档文件并替换已有的同名文件,应该使用cr组合选项。 -
-r (replace or insert) 替换或插入: 使用
r选项可以将对象文件添加到归档中,如果归档中已存在同名文件,ar会询问是否替换它。如果使用r选项并且归档文件不存在,ar会报错。 -
-s (write an index or append) 写入索引或追加:
s选项用于写入或更新归档文件的索引,这个索引用于加速提取和查询操作。当使用s选项时,ar不会询问任何问题,直接执行。 -
-x (extract or delete) 提取或删除: 使用
-x选项可以从归档文件中提取所有对象文件。如果配合单个文件名使用,可以只提取那个特定的文件。 -
-t (list) 列出:
t选项用于列出归档文件中的所有文件,不提取它们。
-
创建归档文件:
ar cr libmylib.a file1.o file2.o这里
cr是创建(create)和替换(replace)的缩写,libmylib.a是输出的归档文件名,file1.o和file2.o是要添加到归档中的对象文件。 -
向归档文件添加文件:
ar r libmylib.a file3.or是添加(replace)的缩写,用于向已存在的归档文件libmylib.a中添加file3.o。 -
从归档文件提取文件:
ar x libmylib.ax是提取(extract)的缩写,用于从libmylib.a中提取所有文件。 -
查看归档文件内容:
ar t libmylib.at是列出(list)的缩写,用于显示归档文件libmylib.a中的所有文件。 -
删除归档中的文件:
ar d libmylib.a file1.od是删除(delete)的缩写,用于从归档文件libmylib.a中删除file1.o。 -
移动归档中的文件:
ar m libmylib.a file1.o file2.om是移动(move)的缩写,用于在归档文件libmylib.a内部移动file1.o和file2.o到指定位置。 -
插入文件到归档的指定位置:
ar i libmylib.a file3.oi是插入(insert)的缩写,与m选项类似,但通常用于将文件插入到归档的特定位置。 -
设置归档文件的选项:
ar S libmylib.a file1.oS是在添加文件时不替换现有文件的缩写。 -
使用通配符添加文件:
ar cr libmylib.a *.o可以使用通配符(如
*.o)将所有.o文件添加到归档中。
ar 工具非常灵活,可以用于多种不同的场景,包括但不限于创建和管理静态库。
3、使用静态库
使用静态链接库:
g++ -static main.o libmyfunction.a -o main
其中,-static 选项强制 GCC 编译器使用静态链接库。
注意,如果 GCC 编译器提示无法找到 libmyfunction.a,还可以使用-L(大写的 L)选项用于向 GCC 编译器指明静态链接库的存储位置(可以借助 pwd 指令查看具体的存储位置);
-l(小写的 L)选项用于指明所需静态链接库的名称,注意这里的名称指的是 xxx 部分,且建议将 -l 和 xxx 直接连用(即 -lxxx),中间不需有空格。
四、创建和使用动态库
1、地址无关PIC
地址无关PIC(Position Independent Code,位置独立代码)
无论何种操作系统上,使用动态链接生成的目标文件中凡是涉及第三方库的函数调用都是地址无关的。假如我们自己编写的程序名为Program 1,Program 1中调用了C标准库的printf(),在生成的目标文件中,不会立即确定printf()的具体地址,而是在运行时去装载这个函数,在装载阶段确定printf()的地址。这里提到的地址指的是进程在内存上的虚拟地址。动态链接库的函数地址在编译时是不确定的,在装载时,装载器根据当前地址空间情况,动态地分配一块虚拟地址空间。
而静态链接库其实是在编译时就确定了库函数地址。比如,我们使用了printf()函数,printf()函数对应有一个目标文件printf.o,静态链接时,会把printf.o链接打包到可执行文件中。在可执行文件中,printf()函数相对于文件头的偏移量是确定的,所以说它的地址在编译链接后就是确定的。
2、创建动态库
总的来说,动态链接库的创建方式有 2 种。
1)直接使用源文件创建动态链接库,采用 gcc 命令实现的基本格式如下:
gcc -fpic -shared 源文件名... -o 动态链接库名
其中,
-shared 选项用于生成动态链接库;
-fpic(还可写成 -fPIC)选项的功能是,令 GCC 编译器生成动态链接库(多个目标文件的压缩包)时,表示各目标文件中函数、类等功能模块的地址使用相对地址,而非绝对地址。这样,无论将来链接库被加载到内存的什么位置,都可以正常使用。
例如,将前面项目中的 greeting.cpp 、name.cpp 这 2 个源文件生成一个动态链接库,执行命令为:
wohu@ubuntu:~/cpp/src$ g++ -fPIC -shared name.cpp greeting.cpp -o libmyfunction.so
wohu@ubuntu:~/cpp/src$ ls
function.h greeting.cpp libmyfunction.a libmyfunction.so main.cpp name.cpp
注意,动态链接库的命名规则和静态链接库完全相同,只不过在 Linux 发行版系统中,其后缀名用 .so 表示;Windows 系统中,后缀名为 .dll。
2)先使用 gcc -c 指令将指定源文件编译为目标文件,再由目标文件生成动态链接库
注意,为了后续生成动态链接库并能正常使用,将源文件编译为目标文件时,也需要使用 -fpic 选项。
wohu@ubuntu:~/cpp/src$ g++ -c -fPIC name.cpp greeting.cpp
wohu@ubuntu:~/cpp/src$ ls
function.h greeting.cpp greeting.o libmyfunction.a main.cpp name.cpp name.o
在此基础上,接下来利用上一步生成的目标文件,生成动态链接库:
wohu@ubuntu:~/cpp/src$ g++ -shared greeting.o name.o -o libmyfunction.so
wohu@ubuntu:~/cpp/src$ ls
function.h greeting.cpp greeting.o libmyfunction.a libmyfunction.so main.cpp name.cpp name.o
3、动态库加载方式
1、windows平台
创建一个动态链接库,会生成x.dll,x.lib
动态链接库有两种加载方式:
1.一种是静态加载,就是在编译的时候就载入动态链接库。此种方法可调用类方法.
可执行程序静态加载动态链接库需要三个文件 x.dll, x.lib, x.h
可执行程序的头文件加入:
#include “x.h”
#pragma comment(lib,“x.lib”)
编译时还要附加库目录,防止程序编译时无法找到x.dll。
2 .动态加载
只需要x.dll文件。
在程序执行需要该动态链接库的地方加载x.dll。
然后获取需要的x.dll库里面的函数或数据.
该方法不能调用类方法.
可执行程序调用了动态链接库,其运行不能缺少动态链接库.
2、Linux平台
在Linux平台上,动态库的加载方式主要分为静态加载和动态加载两种:
静态加载(Static Linking)
静态加载指的是在编译时将动态库链接到程序中。这种方式下,程序在运行时搜索和加载动态库。静态加载通常使用以下步骤:
- 编译源代码为对象文件,使用
-fpic或-fPIC选项以生成与位置无关的代码(Position Independent Code,PIC)。 - 使用
-shared选项将对象文件打包成动态库(.so文件)。 - 在编译程序时,使用
-l选项链接到动态库,例如gcc program.c -o program -lmylib。
动态加载(Dynamic Linking)
动态加载指的是在程序运行到加载处时才加载动态库。这种方式使得程序在编译时不需要依赖库文件,而是在运行时根据需要加载。动态加载通常使用以下步骤:
- 在程序中使用
dlopen函数在运行时加载动态库。 - 使用
dlsym函数获取库中函数的地址。 - 使用完毕后,使用
dlclose函数关闭动态库。 - 如果发生错误,使用
dlerror函数获取错误信息。
动态加载允许程序在运行时根据需要加载和卸载库,而静态加载则在链接时就将库链接到程序中。
4、使用动态库
通过前面章节的学习我们知道,动态链接库的使用场景就是和项目中其它源文件或目标文件一起参与链接。以前面例子为例,前面我们将 greeting.cpp、name.cpp 打包到了 libmyfunction.so 动态链接库中,此时该项目中仅剩 main.cpp 源程序文件,因此执行 demo 项目也就演变成了将 main.cpp 和 libmyfunction.so 进行链接,进而生成可执行文件。
注意,function.h 头文件并不直接参与编译,因为在程序的预处理阶段,已经对项目中需要用到的头文件做了处理。
执行如下指令,即可借助动态链接库成功生成可执行文件:
wohu@ubuntu:~/cpp/src$ g++ main.cpp libmyfunction.so -o main
wohu@ubuntu:~/cpp/src$ ls
function.h greeting.cpp greeting.o libmyfunction.a libmyfunction.so main main.cpp name.cpp name.o
注意,生成的可执行文件 main 通常无法直接执行,例如:
wohu@ubuntu:~/cpp/src$ ./main
./main: error while loading shared libraries: libmyfunction.so: cannot open shared object file: No such file or directory
可以看到,执行过程中无法找到 libmyfunction.so 动态链接库。通过执行 ldd main 指令,可以查看当前文件在执行时需要用到的所有动态链接库,以及各个库文件的存储位置:
wohu@ubuntu:~/cpp/src$ ldd main
linux-vdso.so.1 => (0x00007fffb17ea000)
libmyfunction.so => not found
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f548673a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5486370000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5486067000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5486abc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5485e51000)
可以看到,main 文件的执行需要 7 个动态链接库的支持,其中就包括 libmyfunction.so,但该文件无法找到,因此 main 执行会失败。
运行由动态链接库生成的可执行文件时,必须确保程序在运行时可以找到这个动态链接库。常用的解决方案有如下几种:
将链接库文件移动到标准库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
在终端输入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
其中 xxx 为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效);
修改 ~/.bashrc 或 ~/.bash_profile 文件,即在文件最后一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
其中 xxx 为动态链接库文件的绝对存储路径,保存之后,执行 source bashrc 指令(此方式仅对当前登陆用户有效)。
在本示例中采用第二种方案,即在终端输入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wohu/cpp/src
即可使 main 成功执行。
5、动态库版本信息
dynamic library version information = <M>.<m>.<p>
其中,每个助记符可能使用一个或多个数字来表示
M:主版本号
m:次版本号
p:补丁(很小的代码改动)版本号
动态库的SONAME
library soname = lib + <library name> + .so + <library major version digit(s)>
举例来说:
库文件libz.so.1.2.3.4的SONAME则是libz.so.1
实际上只有主版本号的数字在库SONAME中起作用,这意味着即使库的次版本号是不同的,也可能使用同一个SONAME值来表示。
动态库的SONAME通产由链接器嵌入二进制库文件的专有ELF字段中。通常使用特定的链接器选项,将表示库SONAME的字符串传递给链接器。
五、注意事项
1、如果你的静态库可能会被动态库使用,那么静态库编译的时候需要-fPIC选项。
静态库其实是一系列.o文件的打包,将.o文件制作成静态库的过程是不会发生链接的。
所以链接静态库就约等于将静态库解包后的.o文件一个一个全部链接。
-fPIC选项的生效阶段在编译阶段,使用-fPIC选项编译的.o文件会生成位置无关的代码
如果一个动态库需要链接一个静态库,那么就相当于动态库去链接一系列的.o文件,而编译动态库所需的.o文件时一般是推荐使用-fPIC编译选项的。
2、链接多个静态库时顺序问题
如果lib1.a中func1调用lib2.a的实现,则链接顺序为
gcc -o main main.o lib1.a lib2.a
否则会提示undefined reference to `func1'
在静态链接命令中给出所依赖的库时,需要注意库之间的依赖顺序,依赖其他库的库一定要放到被依赖库的前面,这样才能真正避免undefined reference的错误,完成编译链接。
3、在c++代码中链接c语言的库
如果你的库文件由c代码生成的,则在c++代码中链接库中的函数时,也会碰到undefined reference的问题。
原因就是main.cpp为c++代码,调用了c语言库的函数,因此链接的时候找不到,解决方法:即在main.cpp中,把与c语言库test.a相关的头文件包含添加一个extern "C"的声明即可。
g++ -o main main.cpp test.a
再编译会发现,问题已经成功解决。
4、同时链接静态库和动态库
g++ XXX.cpp -Fpic -o XXX.o -ldl -Wl,-Bstatic -laaa -Wl,-Bdynamic
同时链接动态库 libdl.a 和静态库 libaaa.a
其中,-Wl,-Bstatic 表示后面的库需要静态链接,-Wl,-Bdynamic 表示后面的库需要动态链接。注意系统的运行库使用动态连接的方式,所以当动态库在静态库前面连接时,必须在命令行最后使用动态连接的命令才能正常连接。最后的-Wl,-Bdynamic表示将缺省库链接模式恢复成动态链接。
-Wl是告诉编译器把后面的参数传递给链接器(ld)。因为在编译 C++ 程序的时候,编译器(g++)调用了一个叫链接器(ld)的程序,它的作用是把各种对象文件、库文件链接在一起,生成可执行文件。
-Wl就是为了把编译器的参数传递给链接器,相当于直接传递给链接器一样,其后的参数都是传递给链接器的选项。例如,-Wl,-rpath-link,-Wl,-Bdynamic等选项,都是用来控制链接器行为的选项。
5、使用链接过动态库的静态库
如果有静态库libXXX.a,它用了其他的动态库的函数,比如libAA.so的AA()函数,libBB.so的BB()函数,libCC.so的CC()函数,那么,该libXXX.a对这些动态库的调用仍是动态调用,而不是把动态库的相关函数copy到自己身上。任何其他程序,想用libXXX.a,链接时都需要链接libXXX.a时加上所依赖的动态库。
6、静态库和动态库链接时的搜索路径
现代连接器在处理动态库时将链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定链接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。
1) 静态库链接时搜索路径
1. ld会去找gcc命令中的参数-L
2. 再找gcc的环境变量LIBRARY_PATH
3. 再找内定目录 /lilb、/usr/lib、/usr/local/lib 这是当初compile gcc时写在程序内的
2)程序执行时动态连接库的搜索路径
- RPATH: 写在elf文件中
- LD_LIBRARY_PATH: 环境变量
- RUNPATH: 写在elf文件中
- ldconfig的缓存/etc/ld.so.cache中
/usr/lib和/lib
1、 编译目标代码时指定的动态库搜索路径
gcc 使用-R或-rpath选项来在编译时就指定库的查找路径,并且该库的路径信息保存在可执行文件中,运行时它会直接到该路径查找库。
// 使用以下两种方式都可以,-Wl选项告诉编译器将后面的参数传递给连接器
#gcc -o main main.c -L. ladd -Wl,-R.
#gcc -o main main.c -L. ladd -Wl,rpath=.
#gcc -o main main.c -L. ladd -Wl,rpath .
2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
LD_LIBRARY_PATH设定是全局的,过多的使用可能会影响到其他应用程序的运行,所以多用在调试。
// 当前调试时可以设置LD_LIBRARY_PATH环境变量
#LD_LIBRARY_PATH=.
#export LD_LIBRARY_PATH=.
// 或
#setenv LD_LIBRARY_PATH=.
3. ldconfig命令缓存的so配置/etc/ld.so.cache,ldconfig主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库,进而创建出动态装入程序(ld.so)所需的连接和缓存文件。缓存文件默认为 /etc/ld.so.cache。
3) 库的链接时路径和运行时路径
7、如何判断静态库或者目标文件是否-fPIC编译
readelf --relocs XXX.a | egrep '(GOT|PLT|JU?MP_SLOT)'
上句大多数时候(和平台有关)可以正确判断是否是以fPIC选项编译的,如果输出为空,基本可以表明不是以fPIC选项编译的,若有输出,基本上表明是以fPIC选项编译的。另外,由于静态库是多个目标文件的打包,所以最好把静态库解包之后再对每个目标文件进行判断,这样比较准确。
如果要用在动态库中,o文件和a文件都应该以fPIC选项编译。 fPIC是编译选项也是链接选项,如果编译的时候加了fPIC,链接的时候也必须加上。
8、开源库如何配置
linux ./configure 的参数详解_linux configure.sh-CSDN博客
安装开源常用流程如下:
./configuremakemake install
configure 脚本负责在你使用的系统上准备好软件的构建环境。确保接下来的构建和安装过程所需要的依赖准备好,并且搞清楚使用这些依赖需要的东西。
当 configure 配置完毕后,使用 make 命令执行构建。这个过程会执行在 Makefile 文件中定义的一系列任务将软件源代码编译成可执行文件。源码包一般没有一个最终的 Makefile 文件,一般是一个模版文件 Makefile.in 文件,然后 configure 根据系统的参数生成一个定制化的 Makefile 文件。make install 命令就是将可执行文件、第三方依赖包和文档复制到正确的路径。
configure和make 脚本文件非常复杂,一般是通过autotools 的工具集打包的。这个工具集包含 autoconf 、automake 等工具。
config脚本:先创建一个描述文件 configure.ac 来描述 configure 需要做的事情。configure.ac 使用 m4sh 写,m4sh 是 m4 宏命令和 shell 脚本的组合。使用 autoconf 将 configure.ac 生成 configure 脚本。
make脚本:先写一个 Makefile.am 脚本,然后通过 automake 工具生成 Makefile.in 脚本。
通过脚本的help选项可以知道脚本的配置方式,
./configure –help
例如需要修改CFLAGS选项支持-fPIC只需要
./configure CFLAGS=-fPIC
prefix选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr/local/bin,库文件默认放在/usr/local/lib,配置文件默认放在/usr/local/etc,其它的资源文件放在/usr/local/share,比较凌乱。
./configure --prefix=/usr/local/test
可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。
--with-includes=DIRECTORIES
DIRECTORIES 是一系列冒号分隔的目录,这些目录将被加入编译器的头文件 搜索列表中.如果你有一些可选的包(比如 GNU Readline)安装在 非标准位置,你就必须使用这个选项,以及可能还有相应的 --with-libraries 选项.
例子:--with-includes=/opt/gnu/include:/usr/sup/include.
--with-libraries=DIRECTORIES
DIRECTORIES 是一系列冒号分隔的目录,这些目录是用于查找库文件的. 如果你有一些包安装在非标准位置,你可能就需要使用这个选项 (以及对应的--with-includes选项).
9、链接动态库与使用动态库版本判断
Linux环境中动态库文件(.so文件)的realname,soname和linkname_linux 查看so库的realname-CSDN博客
linkname:
在链接时使用,一般格式为lib$(name).so,通常是$(realname)文件或者$(soname)文件的软链接。
当使用-l$(name)选项时,链接器会从相应目录链接lib$(name).so的文件,如果该文件不存在,则会链接lib$(name).a文件,如果该文件也不存在,会发生链接错误。
如果链接的是lib$(name).a文件,在运行程序时也就没$(realname)文件和$(soname)文件什么事了。当时使用-l$(name)链接库文件时,链接器会读取lib$(name).so文件中的soname值,并将其记录在生成的程序中。
realname:
实际等同于库文件的filename,是在库文件生成时就被指定的,如:gcc -shared -o $(realname) dependence flags
realname的一般格式为 lib$(name).so.$(major).$(minor).$(revision),$(name)是动态库的名字,$(major).$(minor).$(revision)分别表示主版本号,子版本号和修正版本号
soname:
在库文件生成时被指定,如:gcc -shared -o $(realname) dependence flags -Wl,-soname,$(soname)
其一般格式为lib$(name).so.$(major).$(minor),soname会被写入库文件中
可以使用readelf -d $(realname)查看库文件的soname
不同realname的库文件可以有相同的soname,有利于库文件的升级和兼容,例如当版本从1.0.0升级到1.0.1,库文件的接口没有变化(或者接口增加,但原有接口不变)的情况下,可以指定相同的soname,这样使用1.0.0生成的程序仍然可以运行。
soname在链接和加载库文件时使用,当时使用-l$(name)链接库文件时,链接器会读取lib$(name).so文件中的soname值,并将其记录在生成的程序中,当运行程序时,会从相应的目录加载名为$(soname)的文件,所以,在运行程序之前,$(soname)的库文件必须已生成。
可以采用软连接的方式生成该文件,如:ln -s $(realname) $(soname)
也可以使用ldconfig命令自动生成,如 ldconfig -n $(dir) 会生成$(dir)目录下所有库文件对应的$(soname)文件。
10、获取GCC(或make)时,实际编译器和链接器正在执行的命令
方法一:通用方法
使用dry run,如下
$ make -n
这将显示make 命令正在试图做的事情。
通过 $ make -h 命令,查看帮助可知,make -n实际并不运行任何命令,只是把make试图做的事情显示出来。
方法二:特定方法,适用于使用autotools产生的库makefile文件
使用autotools(你必须发布./configure)产生的库makefile文件常常有一个verbose选项,所以基本上,使用
make VERBOSE=1 或 make V=1
你将获取到全部命令。但是这取决于makefile产生的方式。
make命令的-d选项可能会有帮助的,但是会有很长的输出,而且也不是实际运行的命令,而是大量的调试信息。
相关文章:
静态库与动态库总结
一、库文件和头文件 所谓库文件,可以将其理解为压缩包文件,该文件内部通常包含不止一个目标文件(也就是二进制文件)。 值得一提的是,库文件中每个目标文件存储的代码,并非完整的程序,而是一个…...
深入解析tcpdump:网络数据包捕获与分析的利器
引言 在网络技术日新月异的今天,网络数据包的捕获与分析成为了网络管理员、安全专家以及开发人员不可或缺的技能。其中,tcpdump作为一款强大的网络数据包捕获分析工具,广泛应用于Linux系统中。本文将从技术人的角度,详细分析tcpdu…...
【漏洞复现】科立讯通信有限公司指挥调度管理平台uploadgps.php存在SQL注入
0x01 产品简介 科立讯通信指挥调度管理平台是一个专门针对通信行业的管理平台。该产品旨在提供高效的指挥调度和管理解决方案,以帮助通信运营商或相关机构实现更好的运营效率和服务质量。该平台提供强大的指挥调度功能,可以实时监控和管理通信网络设备、…...
什么是自然语言处理(NLP)?详细解读文本分类、情感分析和机器翻译的核心技术
什么是自然语言处理? 自然语言处理(Natural Language Processing,简称NLP)是人工智能的一个重要分支,旨在让计算机理解、解释和生成人类的自然语言。打个比方,你和Siri对话,或使用谷歌翻译翻译一…...
【linux】gcc快速入门教程
目录 一.gcc简介 二.gcc常用命令 一.gcc简介 gcc 是GNU Compiler Collection(GNU编译器套件)。就是一个编译器。编译一个源文件的时候可以直接使用,但是源文件数量太多时,就很不方便,于是就出现了make 工具 二.gcc…...
【多维动态规划】Leetcode 97. 交错字符串【中等】
交错字符串 给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串 子字符串 是字符串中连续的 非空 字符序列。 s s1 s2 … snt…...
【JavaScript脚本宇宙】精通前端开发:六大热门CSS框架详解
前端开发的利器:深入了解六大CSS框架 前言 在现代Web开发中,选择适合的前端框架和工具包是构建高效、响应式和美观的网站或应用程序的关键。本文将详细介绍六个广受欢迎的CSS框架:Bootstrap、Bulma、Tailwind CSS、Foundation、Materialize…...
开发技术-Java集合(List)删除元素的几种方式
文章目录 1. 错误的删除2. 正确的方法2.1 倒叙删除2.2 迭代器删除2.3 removeAll() 删除2.4 removeIf() 最简单的删除 3. 总结 1. 错误的删除 在写代码时,想将其中的一个元素删除,就遍历了 list ,使用了 remove(),发现效果并不是想…...
c++ 递归
递归函数是指在函数定义中调用自身的函数。C语言也支持递归函数。 下面是一个使用递归函数计算阶乘的例子: #include <iostream> using namespace std;int factorial(int n) {// 基本情况,当 n 等于 0 或 1 时,阶乘为 1if (n 0 || n…...
RedHat9 | podman容器
1、容器技术介绍 传统问题 应用程序和依赖需要一起安装在物理主机或虚拟机上的操作系统应用程序版本比当前操作系统安装的版本更低或更新两个应用程序可能需要某一软件的不同版本,彼此版本之间不兼容 解决方式 将应用程序打包并部署为容器容器是与系统的其他部分…...
边缘计算项目有哪些
边缘计算项目在多个领域得到了广泛的应用,以下是一些典型的边缘计算项目案例: 1. **智能交通系统**:通过在交通信号灯、监控摄像头等设备上部署边缘计算,可以实时分析交通流量,优化交通信号控制,减少拥堵&…...
计算fibonacci数列每一项时所需的递归调用次数
斐波那契数列是一个经典的数列,其中每一项是前两项的和,定义为: [ F(n) F(n-1) F(n-2) ] 其中,( F(0) 0 ) 和 ( F(1) 1 )。 对于计算斐波那契数列的第 ( n ) 项,如果使用简单的递归方法,其时间复杂度是…...
【教学类65-05】20240627秘密花园涂色书(中四班练习)
【教学类65-03】20240622秘密花园涂色书03(通义万相)(A4横版1张,一大 68张纸136份)-CSDN博客 背景需求: 打印以下几款秘密花园样式(每款10份)给中四班孩子玩一下,看看效果 【教学类…...
Python 学习之基础语法(一)
Python的语法基础主要包括以下几个方面,下面将逐一进行分点表示和归纳: 一、基本语法 1. 注释 a. 单行注释:使用#开头,例如# 这是一个单行注释。 b. 多行注释:使用三引号(可以是三个单引号或三个双引号&…...
日志分析-windows系统日志分析
日志分析-windows系统日志分析 使用事件查看器分析Windows系统日志 cmd命令 eventvwr 筛选 清除日志、注销并重新登陆,查看日志情况 Windows7和Windowserver2008R2的主机日志保存在C:\Windows\System32\winevt\Logs文件夹下,Security.evtx即为W…...
【ARM】MDK工程切换高版本的编译器后出现error A1137E报错
【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决工程从Compiler 5切换到Compiler 6进行编译时出现一些非语法问题上的报错。 2、 问题场景 对于一些使用Compiler 5进行编译的工程,要切换到Compiler 6进行编译的时候,原本无任何报错警告…...
深入 SSH:解锁本地转发、远程转发和动态转发的潜力
文章目录 前言一、解锁内部服务:SSH 本地转发1.1 什么是 SSH 本地转发1.2 本地转发应用场景 二、打开外部访问大门:SSH 远程转发2.1 什么是 SSH 远程转发2.2 远程转发应用场景 三、动态转发:SSH 让你拥有自己的 VPN3.1 什么是 SSH 动态转发3.…...
python如何把一个函数的返回值,当成这个函数的参数值
python如何把一个函数的返回值,当成这个函数的参数值 1. 递归调用 递归是一种函数自己调用自己的方法。在递归调用中,你可以将前一次调用的返回值作为下一次调用的参数。 def recursive_function(x):# 函数逻辑if 条件满足:return 结果else:return rec…...
【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例应用
随着航空、航天、近地空间遥感平台的持续发展,遥感技术近年来取得显著进步。遥感数据的空间、时间、光谱分辨率及数据量均大幅提升,呈现出大数据特征。这为相关研究带来了新机遇,但同时也带来巨大挑战。传统的工作站和服务器已无法满足大区域…...
SpringBoot: Eureka入门
1. IP列表 公司发展到一定的规模之后,应用拆分是无可避免的。假设我们有2个服务(服务A、服务B),如果服务A要调用服务B,我们能怎么做呢?最简单的方法是让服务A配置服务B的所有节点的IP,在服务A内部做负载均衡调用服务B…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁
赛门铁克威胁猎手团队最新报告披露,数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据,严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能,但SEMR…...
