技巧:Linux 动态库与静态库制作及使用详解

技巧:Linux 动态库与静态库制作及使用详解

标准库的三种连接方式及静态库制作与使用方法

Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 Linux 版本下不兼容的问题; 2)在 Linux 静态库的制作过程中发现有别于 Windows 下静态库的制作方法;3)在 Linux 应用程序链接第三方库或者其他静态库的时候发现链接顺序的烦人问题。本文就这三个问题针对 Linux 下标准库链接和如何巧妙构建 achrive(*.a) 展开相关介绍。

两个要知道的基本知识

Linux 应用程序因为 Linux 版本的众多与各自独立性,在工程制作与使用中必须熟练掌握如下两点才能有效地工作和理想地运行。

  1. Linux 下标准库链接的三种方式(全静态 , 半静态 (libgcc,libstdc++), 全动态)及其各自利弊。
  2. Linux 下如何巧妙构建 achrive(*.a),并且如何设置链接选项来解决 gcc 比较特别的链接库的顺序问题。

三种标准库链接方式选项及对比

为了演示三种不同的标准库链接方式对最终应用程序产生的区别, 这里用了一个经典的示例应用程序 HelloWorld 做演示,见 清单 1 HelloWorld。 整个工程可以在文章末尾下载。

清单 1. HelloWorld
 #include <stdio.h>
 #include <iostream>
 using std::cout;
 using std::endl; 

 int main(int argc, char* argv[])
 {
  printf("HelloWorld!(Printed by printf)\n"); 

  cout<<"HelloWorld!(Printed by cout)"<<endl; 

  return 0;
 }

三种标准库链接方式的选项及区别见 表 1

表 1. 三种标准库链接方式的选项及区别
标准库连接方式 示例连接选项 优点 缺点
全静态 -static -pthread -lrt -ldl 不会发生应用程序在 不同 Linux 版本下的标准库不兼容问题。 生成的文件比较大,
应用程序功能受限(不能调用动态库等)
全动态 -pthread -lrt -ldl 生成文件是三者中最小的 比较容易发生应用程序在
不同 Linux 版本下标准库依赖不兼容问题。
半静态 (libgcc,libstdc++) -static-libgcc -L. -pthread -lrt -ldl 灵活度大,能够针对不同的标准库采取不同的链接策略,
从而避免不兼容问题发生。
结合了全静态与全动态两种链接方式的优点。
比较难识别哪些库容易发生不兼容问题,
目前只有依靠经验积累。
某些功能会因选择的标准库版本而丧失。

上述三种标准库链接方式中,比较特殊的是 半静态链接方式,主要在于其还需要在链接前增加额外的一个步骤:
ln -s `g++ -print-file-name=libstdc++.a`,作用是将 libstdc++.a(libstdc++ 的静态库)符号链接到本地工程链接目录。
-print-file-name 在 gcc 中的解释如下:
-print-file-name=<lib> Display the full path to library <lib>

为了区分三种不同的标准库链接方式对最终生成的可执行文件的影响,本文从两个不同的维度进行分析比较:

维度一:最终生成的可执行文件对标准库的依赖方式(使用 ldd 命令进行分析)

ldd 简介:该命令用于打印出某个应用程序或者动态库所依赖的动态库
涉及语法:ldd [OPTION]... FILE...
其他详细说明请参阅 man 说明。

三种标准库链接方式最终产生的应用程序的可执行文件对于标准库的依赖方式具体差异见 图 1图 2图 3所示:

图 1. 全静态标准库链接方式

图 2. 全动态标准库链接方式

图 3. 半静态(libgcc,libstdc++) 标准库链接方式

通过上述三图,可以清楚的看到,当用 全静态标准库的链接方式时,所生成的可执行文件最终不依赖任何的动态标准库,
全动态标准库的链接方式会导致最终应用程序可执行文件依赖于所有用到的标准动态库。
区别于上述两种方式的 半静态链接方式则有针对性的将 libgcc 和 libstdc++ 两个标准库非动态链接。
(对比 图 2图 3,可见在 图 3中这两个标准库的动态依赖不见了)

从实际应用当中发现,最理想的标准库链接方式就是半静态链接,通常会选择将 libgcc 与 libstdc++ 这两个标准库静态链接,
从而避免应用程序在不同 Linux 版本间标准库依赖不兼容的问题发生。

维度二 : 最终生成的可执行文件大小(使用 size 命令进行分析)

size 简介:该命令用于显示出可执行文件的大小
涉及语法:size objfile...
其他详细说明请参阅 man 说明。

三种标准库链接方式最终产生的应用程序的可执行文件的大小具体差异见 图 4图 5图 6所示:

图 4. 全静态标准库链接方式

图 5. 全动态标准库链接方式

图 6. 半静态(libgcc,libstdc++) 标准库链接方式

通过上述三图可以看出,最终可执行文件的大小随最终所依赖的标准动态库的数量增加而减小。
从实际应用当中发现,最理想的是 半静态链接方式,因为该方式能够在避免应用程序于
不同 Linux 版本间标准库依赖不兼容的问题发生的同时,使最终生成的可执行文件大小最小化。

示例链接选项中所涉及命令(引用 GCC 原文):

-llibrary
-l library:指定所需要的额外库
-Ldir:指定库搜索路径
-static:静态链接所有库
-static-libgcc:静态链接 gcc 库
-static-libstdc++:静态链接 c++ 库
关于上述命令的详细说明,请参阅 GCC 技术手册

回页首

Linux 下静态库(archive)的制作方式:

涉及命令:ar

ar 简介:处理创建、修改、提取静态库的操作

涉及选项:
t - 显示静态库的内容
r[ab][f][u] - 更新或增加新文件到静态库中
[s] - 创建文档索引
ar -M [<mri-script] - 使用 ar 脚本处理
其他详细说明请参阅 man 说明。

示例情景:

假设现有如 图 7所示两个库文件

图 7. 示例静态库文件

图 7中可以得知,CdtLog.a 只包含 CdtLog.o 一个对象文件 , 而 xml.a 包含 TXmlParser.o 和 xmlparser.o 两个对象文件
现将 CdtLog.o 提取出来,然后通过 图 8方式创建一个新的静态库 demo.a,可以看出,demo.a 包含的是 CdtLog.o 以及 xml.a,
而不是我们所预期的 CdtLog.o,TXmlParser.o 和 xmlparser.o。这正是区别于 Windows 下静态库的制作。

图 8. 示例静态库制作方式 1

这样的 demo.a 当被链接入某个工程时,所有在 TXmlParser.o 和 xmlparser.o 定义的符号都不会被发现,从而会导致链接错误,
提示无法找到对应的符号。显然,通过图 8 方式创建 Linux 静态库是不正确的。

正确的方式有两种:

  1. 将所有静态库中包含的对象文件提取出来然后重新打包成新的静态库文件。
  2. 用一种更加灵活的方式创建新的静态库文件:ar 脚本

显然,方式 1 是比较麻烦的,因为涉及到太多的文件处理,可能还要通过不断创建临时目录用于保存中间文件。
推荐使用如 清单 2 createlib.sh所示的 ar 脚本方式进行创建:

清单 2 createlib.sh
 rm demo.a
 rm ar.mac
 echo CREATE demo.a > ar.mac
 echo SAVE >> ar.mac
 echo END >> ar.mac
 ar -M < ar.mac
 ar -q demo.a CdtLog.o
 echo OPEN demo.a > ar.mac
 echo ADDLIB xml.a >> ar.mac
 echo SAVE >> ar.mac
 echo END >> ar.mac
 ar -M < ar.mac
 rm ar.mac

如果想在 Linux makefile 中使用 ar 脚本方式进行静态库的创建,可以编写如 清单 3 BUILD_LIBRARY所示的代码:

清单 3 BUILD_LIBRARY
 define BUILD_LIBRARY
 $(if $(wildcard [email protected]),@$(RM) [email protected])
 $(if $(wildcard ar.mac),@$(RM) ar.mac)
 $(if $(filter %.a, $^),
 @echo CREATE [email protected] > ar.mac
 @echo SAVE >> ar.mac
 @echo END >> ar.mac
 @$(AR) -M < ar.mac
 )
 $(if $(filter %.o,$^),@$(AR) -q [email protected] $(filter %.o, $^))
 $(if $(filter %.a, $^),
 @echo OPEN [email protected] > ar.mac
 $(foreach LIB, $(filter %.a, $^),
 @echo ADDLIB $(LIB) >> ar.mac
 )
 @echo SAVE >> ar.mac
 @echo END >> ar.mac
 @$(AR) -M < ar.mac
 @$(RM) ar.mac
 )
 endef 

 $(TargetDir)/$(TargetFileName):$(OBJS)
    $(BUILD_LIBRARY)

通过 图 9,我们可以看到,用这种方式产生的 demo.a 才是我们想要的结果。

图 9. 巧妙创建的静态库文件结果

回页首

Linux 静态库链接顺序问题及解决方法:

正如 GCC 手册中提到的那样:
It makes a difference where in the command you write this option; the linker
searches and processes libraries and object files in the order they are specified.
Thus, ‘ foo.o -lz bar.o ’ searches library ‘ z ’ after file ‘ foo.o ’ but before
‘ bar.o ’ . If ‘ bar.o ’ refers to functions in ‘ z ’ , those functions may not be loaded.

为了解决这种库链接顺序问题,我们需要增加一些链接选项 :

$(CXX) $(LINKFLAGS) $(OBJS) -Xlinker "-(" $(LIBS) -Xlinker "-)" -o [email protected]

通过将所有需要被链接的静态库放入 -Xlinker "-(" 与 -Xlinker "-)" 之间,可以是 g++ 链接过程中,
自动循环链接所有静态库,从而解决了原本的链接顺序问题。

涉及链接选项:-Xlinker

-Xlinker option
Pass option as an option to the linker. You can use this to supply system-specific
linker options which GCC does not know how to recognize.

回页首

小结

本文介绍了 Linux 下三种标准库链接的方式及各自利弊,同时还介绍了 Linux 下静态库的制作及使用方法,相信能够给
大多数需要部署 Linux 应用程序和编写 Linux Makefile 的工程师提供有用的帮助。

时间: 2024-08-04 02:50:06

技巧:Linux 动态库与静态库制作及使用详解的相关文章

Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf 动态库的后缀为*.so 静态库的后缀为 libxxx.a ldconfig 目录名

Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf  动态库的后缀为*.so  静态库的后缀为 libxxx.a   ldconfig   目录名 转载自:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在这之前,我们需要了解一下源代码到可执行程序之间到底发生了什么神奇而美妙的事情. 在Linux操作系统中,普遍使用ELF格

Linux系统中“动态库”和“静态库”那点事儿【转】

转自:http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在这之前,我们需要了解一下源代码到可执行程序之间到底发生了什么神奇而美妙的事情. 在Linux操作系统中,普遍使用ELF格式作为可执行程序或者程序生成过程中的中间格式.ELF(Executable and Linking Format,可执行连接格式)是UNIX系统实验室(USL)作为应用程序二进制

Linux系统中“动态库”和“静态库”那点事儿

摘自http://blog.chinaunix.net/uid-23069658-id-3142046.html 今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻.在这之前,我们需要了解一下源代码到可执行程序之间到底发生了什么神奇而美妙的事情. 在Linux操作系统中,普遍使用ELF格式作为可执行程序或者程序生成过程中的中间格式.ELF(Executable and Linking Format,可执行连接格式)是UNIX系统实验室(USL)作为应用程序二进制接

linux 的动态库和静态库

库从本质上来说,是一种可执行代码的二进制格式,可以载入内存中执行.库分静态库和动态库两种 静态库:这类库的名字一般是libxxx.a, xxx是库的名字.利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合到目标代码中.优点是,变异后的执行程序不需要外部的函数库支持.确定是,如果静态函数库改变了,那么程序需要重新编译. 动态库:这类库的名字一般是libxxx.M.N.so,xxx是库的名字,M是库的主版本号,N是库的副版本号.(版本号不是必须的).动态库是在程序运行时动态申请并加

linux动态库与静态库使用比较

在windows下,动态库dll的使用往往伴随着lib的指引,而linux使用动态库和静态库则有较大的不同. linux静态库和动态库的区别 1. 静态库 名字一般是libxxx.a:利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,编译后的执行程序不需要外部的函数库支持,但是,升级比较麻烦.每一次版本更新都需要重新编译. 2. 动态库 名字一般是libxxx.so;动态库没有被编译进最终程序,只有在需要的时候,动态加载到内存中.编译后的程序不包含动态库部分,程

Linux环境编译动态库和静态库总结

对Linux环境动态库和静态库的一些基础知识做一些总结, 首先总结静态库的编译步骤. 1 先基于.cpp或者.c文件生成对应的.o文件 2将几个.o文件 使用ar -cr命令 生成libname.a文件 libname.a 为静态库, name 为静态库的名字, 可以根据模块功能命名. 举个例子 在当前目录下,有一个mymethod.h文件 在mymethod.cpp里实现这两个函数 在当前目录下有一个main.cpp文件 为了方便使用,将mymethod.cpp封装成静态库和动态库 首先将my

Windows 下VC++6.0制作、使用动态库和静态库

Windows 下VC++6.0制作.使用动态库和静态库 一.VC++6.0制作.使用静态库 静态库制作 1.如图一在VC++6.0中new一个的为win32 static library工程并新建一个.cpp和一个.h(C++header file)文件 2..cpp程序直接照老师给的打,注意这里需要改错,去掉(long) 3. .h文件需要自己编写格式如下.以head.h为例 #ifndef _HEAD_H_ #define _HEAD_H_ unsigned long unsgn_pow(

Linux下的动态库与静态库

2019-09-25 关键字:生成库.静态库引用.动态库引用 在 C 开发中,“库”是一个经常听到的名词. 所谓的库其实就是一个二进制文件.这个二进制文件的内容是可被其它C程序调用执行的函数.换句话说,库就是一组C代码的打包形式而已,打包是指将源代码以库的形式编译而生成的文件. 不过即使它只是源代码的打包,它也仍然是有系统专有性的,即不同系统下编译出来的库并不能互相兼容使用. C库可分为动态库与静态库两种.引用了外部库的应用程序在编译时会在”链接期“处理库与程序源码之间的关系. 1.静态库 静态

函数库:静态库和动态库

1.函数库 函数库其实就是一些写好的函数集合,方便别人的复用.实现的封装之后,最终的目的都是给别人调用. 2.库的形式 库的形式分:动态链接库和静态链接库. 优点: (1)库文件都是被编译好的二进制文件,别人看不到源代码,可以保持保密:(2)同时不会因为不小心被修改出现问题,便于维护. LINUX 下的库学习: 静态链接库:.a 文件 其实就是将多函数,做编译但是不链接生成的 .o 文件,使用 ar 工具打包为 .a 的文件.编译的阶段,当调用这些库的时候,链接器就会去 .a 的库文件中拿出被调