searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

linux 下动态库与静态库的使用

2023-11-23 08:51:05
9
0
1、库的简介
linux下的库是一种模块化开发程序的有效手段,库是一个二进制文件,里面包含的代码可被外部程序调用;库可以充分利用前人代码成果,进行迭代开发,实现代码的模块化管理。linux库常见的形式有静态库、动态库,它们默认存放于linux 的 /lib64 或者/usr/lib64目录之下。两者是有很大区别的。
1.1 静态库的特点:
  • 编译时把今天库中的相关代码复制到可执行文件中;
  • 程序中已包含代码,运行时不再需要静态库;
  • 程序运行时无需要加载库,运行速度更快;
  • 有多少程序包含该库,该库就有多少个副本,占用更多磁盘空间及内存空间;
  • 静态库升级后,程序需要重新编译链接;
1.2 静态库的使用:
编写程序时,在代码中包含库的函数声明头文件,并直接调用库中函数, 程序编译时如类似的使用 -lhello 。
1.3 动态库的特点
  • 编译时仅仅记录使用到那个动态库的那个符号表,不会复制库中的代码;
  • 可执行程序不包含库中代码,最终尺寸大小较小;
  • 使用方便,升级无需程序重新编译;
1.4 动态库的使用
  • 自动方式:编写程序test.c,在代码中调用库函数;编译代码时链接动态库如libhello.so,动态库和静态库默认在同一目录时,默认调用动态库,如果想调用静态库,链接时加上-static;
动态库在编译的过程中,使用-l指定共享库名,以满足链接的需要(这里的链接不是运行阶段)。
  • 手动方式:代码中直接手动加载共享库,使用dlopen函数加载新库并把它装入内存,传入动态库的完整路径进行加载;这种方式是在运行阶段用dlopen手动以路径的形式打开它,也就是说共享库相当于我们程序运行时内部的一个参数,因此不需要编译时以-l去指定该共享库了,需要注意的是:使用dlopen这种方式时,需要APP加载时使用-ldl加载共享库libdl.so以及头文件dlfcn.h。
void *dlopen(const char*pathname,int mode)第一个参数是动态库地址,第二个参数按功能可以分为三类 可通过按位或进行组合使用;
RTLD_LAZY:暂缓决定,在dlopen返回前,对于动态库中未定义的符号表不执行解析(只对函数引用有效,对于变量引用总是立即解析);
RTLD_NOW:立即解析函数引用, 如果解析不出,dlopen返回NULL并报错;
RTLD_GLOBAL:动态库中定义的模号可被其后打开的其他库库解析;
RTLD_LOCAL:与RTLD_GLOBAL相反,动态库中定义的符号不能被其他打开的库重定位,如果没有明确指明,默认是RTLD_LOCAL;
RTLD_NODELETE 在dlclose()期间不卸载库,并且在以后使用dlopen重新加载库时不初始化库中的静态变量;
RTLD_NOLOAD:不加载库,可以用于测试库是否已经加载,dlopen返回位NULL说明未加载,否则说明已加载。
RTLD_DEEPBIND:在搜索全局符号前先搜索库内的符号,避免同名符号的冲突;
void *dlsym(void *handle, const char *symbol);
dlsym函数负责冻灾加载函数的符号, 第一个参数是句柄(由dlopen返回),第二个参数就是给定的函数名称;
int dlclose(void*handle); dlclose函数负责关闭指定的动态库;
 
2. 库的制作及使用
 
制作静态库比较简单,使用ar命令即可直接生成,比如 ar -rv libhello.a hello.o; 然后在编译可执行文件时进行链接如:gcc main.c libhello.a -o test;
制作动态库的时候并没有把代码拷贝一份到最终生成的可执行文件中,而是在执行的时候才会去加载,动态库的使用很广泛,但是制作一个动态库比静态库麻烦很多。我们本文基于linux为基础进行说明。
gcc -shard -fpic hello.c -o hello.so; 其中-shared即表示创建一个动态库,-fpic是将绝对地址转换成相对地址,这样不通的程序可以在不同的虚拟地址中加载该动态库;动态库在链接成可执行文件如:
gcc main.c -L LIB_PATH -lhello -o test; 这里-L指定动态库的路径,-l用来指定库名称。
 
3. 库包含库形成新库的方法
在我们代码开发的过程中,有时候会想把底层的多个库直接打包成一个新的库中,同一对外提供服务这也是我们敏捷开发过程可能面临的一个问题。其中值得注意的是一下问题:
3.1静态库包含静态库
编译静态库时,只有编译过程,没有链接过程,即静态库在应用其他库的时候,并不会在编译阶段就把引用的其他静态库的符号生成到新的静态库中,它只是简单的将编译后的中间文件进行打包;在最终生成的可执行文件时,需要引用前面所有的库进行符号消解;
3.2 动态库包含静态库
编译动态库的过程,不仅包含了编译过程,还有链接过程,动态库在生成过程会对其引用的符号表进行消解,动态库包含静态后,动态库中就包含了原静态库的所有符号。
 
4. 库使用中典型问题分析
 
4.1 C语言构造函数的使用
C语言中也有类似C++的构造函数,它是由gcc进行的扩展,采用attribute((constructor)) 进行实现, 有constructor修饰的函数会在main函数之前自动加载,与之相对的就是destructor,它会在main函数执行结束或者exit时自动调用。在不指定优先级的情况下,不同的构造函数出现在同一文件中,先出现的后执行,不同文件中的构造函数,编译命令中后出现的C文件中构造函数先执行。利用constructor属性,我们可以定义一些宏模块进行自动注册。这中用法在DPDK中使用非常的普遍。
constructor 机制模块使用及维护都非常的放方便,但是当我们把它编译进静动态库中后,在集成应用可执行文件时候,由于应用没有任何地方调用这些自动调用的接口,调用的时候是可能出问题的。应为constructor修饰的函数存在文件elf文件的constructor区域,在链接的时候由于constructor修饰的函数没有任何显示调用,编译器会把他们优化掉,不起链接我们的自动注册函数,这样生成的elf文件的constructor区域就没有相应的符号表,自然也就无法实现自动调用的目的。-Wl,--whole-archive 与 -Wl,--no-whole-archive可以帮我们解决这个问题。-Wl,--whole-archive 指明其后的所有静态库的函数和变量都强制链接到新的目标文件中, -Wl,--no-whole-archive就是结束这一特性。gcc main.c -lliba.a -Wl,--whole-archive -llibb.a -llibc.a -Wl,--no-whole-archive -o test
 
4.2 库的依赖关系
查找需要连接的符号名是根据-L指定的路径顺序查找;从左往右找(如果符号有依赖,第一个-l库文件最先调用,如果它依赖于其它库,依赖的库应该放在它的后面,在后面继续寻找它的依赖,就像主obj依赖-l库一样;如果符号没有依赖,则第一个库最先调用,它后面的库里如果有同名符号函数则不会生效);不同目录下的同名的库,只取最先调用的-l静态库(从左向右:即依次调用,最后生效的调用:即最右边的),后面同名库被忽略,从左向右查找,如果是主程序块和静态库,不能定位地址就报错: ‘undefined reference to: xxx’。
如果是链接成动态库,则假设该符号在load 的时候地址重定位。如果找不到对应的动态库,则会在load的时候报:“undefined symbol: xxx“这样的错误。
 
4.3 Cmake 编译库的呈现方式
编译库若采用cmake方式,则需要注意target_** 中的 PUBLIC,PRIVATE,INTERFACE使用方式及区别,这些标识主要实在对外暴露的符号内容,影响外部使用。
PRIVATE:私有的。生成 libhello-world.so时,只在 hello_world.c 中包含了 hello.h,libhello-world.so 对外的头文件—hello_world.h 中不包含 hello.h。而且 main.c 不会调用 hello.c 中的函数,或者说 main.c 不知道 hello.c 的存在,那么在 hello-world/CMakeLists.txt 中应该写入:
target_link_libraries(hello-world PRIVATE hello) target_include_directories(hello-world PRIVATE hello)
INTERFACE:接口。生成 libhello-world.so 时,只在libhello-world.so 对外的头文件——hello_world.h 中包含 了 hello.h, hello_world.c 中不包含 hello.h,即 libhello-world.so 不使用 libhello.so 提供的功能,只使用 hello.h 中的某些信息,比如结构体。但是 main.c 需要使用 libhello.so 中的功能。那么在 hello-world/CMakeLists.txt 中应该写入:
target_link_libraries(hello-world INTERFACE hello) target_include_directories(hello-world INTERFACE hello)
PUBLIC:公开的。PUBLIC = PRIVATE + INTERFACE。生成 libhello-world.so 时,在 hello_world.c 和 hello_world.h 中都包含了 hello.h。并且 main.c 中也需要使用 libhello.so 提供的功能。那么在 hello-world/CMakeLists.txt 中应该写入:
target_link_libraries(hello-world PUBLIC hello) target_include_directories(hello-world PUBLIC hello)
 
0条评论
0 / 1000