某某茶叶有限公司欢迎您!
金沙棋牌在线 > 服务器&运维 > Linux下的静态库、动态库和动态加载库

Linux下的静态库、动态库和动态加载库

时间:2020-03-16 13:31

Linux库类型

Linux下可以创建两种类型的库:

  1. 静态库(.a): 在链接期间被应用程序直接链接进可执行文件
  2. 动态链接库(.so): 动态库还分为两种用法: a) 应用程序运行期间链接动态库,但是在编译期间声明动态库的存在,也就是说这种动态库必须在编译时对编译器可见,但编译器却不将此种库编译进可执行文件; b) 在运行期间,动态加载和卸载的库,使用动态加载方法加载。这种库的形式跟动态链接没有本质区别,区别是在调用时,是由用户程序决定何时链接的,而不是由系统链接器自动链接

本文同步发布于我的个人网站:Linux静态库和动态库

所谓静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。当多个程序都调用相同函数时,内存中就会存在这个函数的多个拷贝,这样就浪费了宝贵的内存资源。.so文件是共享库文件(动态链接)。动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息),仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的.so之间建立链接关系。

命名约定

库需要以lib作为开头,而在指定链接命令行参数时,却无需包含开头和扩展名,例如:

gcc src-file.c -lm -lpthread

这个例子中,链接了libmath.alibpthread.a


.a文件是多个.o文件的组合。.o文件就是对象文件,里面包含的内容就是01这样的机器可执行的指令,当程序要执行时还需要进行链接(link).链接就是把多个.o文件链成一个可执行文件。
关于库生成的问题
我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
(1)静态库
简单地说,静态库是一个目标文件的简单集合。因此,首先要解决目标文件。
第一步:将各函数代码所在的源文件编译成目录文件。
例如,对于myfunc.c, myproc.c
gcc -c myfunc.c myproc.c
将得到myfunc.o和myproc.o。
第二步:由ar(archive,归档的意思)把多个目标文件集合起来。
$ar -r libmyjob.a myfunc.o myproc.o
通常,静态库的命名方式应遵守libXXXXX.a格式。应用程序在使用静态库的时候,通常只需要把命名中的XXXXX部分传递给gcc即可。例如:
$gcc –o mywork –lmyjob …
意为让gcc(实际上是gcc调用ld)去连接一个名字为libmyjob.a(或者libmyjob.so)的库。如果库的命名不遵循libXXXXX.a的格式就找不到相应文件。
例子:创建静态库
hello.h为该函数库的头文件。hello.c是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出"
hello XXX!"。main.c为测试库文件的主程序,在主程序中调用了公用函数hello。
程序1:
//hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
程序2:
//hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("hello %s! n",name);
}
程序3:
//main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
实现步骤:
第一步:必须将源程序hello.c通过gcc先编译成.o文件,生成hello.o(静态库/动态库,都是由.o文件创建的);
第二步:由.o文件创建静态库,生成libmyhello.a(静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a)创建静态库用ar命令;
第三步:在程序中使用静态库;(只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件)
第四步:删除静态库文件,程序照常运行,静态库中的公用函数hello已经连接到目标文件main中了。
运行:
[root@localhost moduletest]# ls
hello.c hello.h main.c
[root@localhost moduletest]# gcc -c hello.c
[root@localhost moduletest]# ls
hello.c hello.h hello.o main.c
[root@localhost moduletest]# ar crv libmyhello.a hello.o
a - hello.o
[root@localhost moduletest]# ls
hello.c hello.h hello.o libmyhello.a main.c
[root@localhost moduletest]# gcc main.c libmyhello.a -o main
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]# rm -f libmyhello.a
[root@localhost moduletest]# ls
hello.c hello.h hello.o main main.c
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]#
(2)共享库
共享库的构造复杂一些,通常是一个ELF格式的文件。可以有三种方法生成:
$ld -G
金沙棋牌在线,$gcc -shared
$libtool
用ld最复杂,用gcc -share就简单的多,但是-share并非在任何平台都可以使用。GNU提供了一个更好的工具libtool,专门用来在各种平台上生成各种库。
用gcc的-shared参数:
gcc –shared –o libmyjob.so myjob.o
这样,就通过myjob.o生成了共享库文件libmyjob.so。
特别地,在CYGWIN环境下,仍需要输出符合Windows命名的共享库(动态库),即libXXXXX.dll。如:
gcc –shared –o libmyjob.dll myjob.o
例子:创建动态库(延用上面的程序1,2,3)
实现步骤:
第五步:由.o文件创建动态库文件(命令:gcc -shared -fPCI -o libmyhello.so hello.o);
第六步:在程序中使用动态库;(在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示错误信息而终止程序运行。要将文件libmyhello.so复制到目录/usr/lib中)
运行:
[root@localhost moduletest]# ls
hello.c hello.h hello.o main.c
[root@localhost moduletest]# gcc -shared -fPIC -o libmyhello.so hello.o
[root@localhost moduletest]# ls
hello.c hello.h hello.o libmyhello.so main.c
[root@localhost moduletest]# gcc main.c libmyhello.so -o main
[root@localhost moduletest]# ls
hello.c hello.h hello.o libmyhello.so main main.c
[root@localhost moduletest]# ./main
./main: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
[root@localhost moduletest]# mv libmyhello.so /usr/lib
可以:
[root@localhost moduletest]# ls
hello.c hello.h hello.o main main.c
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]#
或者:
[root@localhost moduletest]# rm -f main
[root@localhost moduletest]# ls
hello.c hello.h hello.o main.c
[root@localhost moduletest]# gcc -Wall -g main.c -lmyhello -o main
[root@localhost moduletest]# ls
hello.c hello.h hello.o main main.c
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]#
注意:
当静态库和动态库同名时, gcc命令将优先使用动态库。
(3)库生成以后的配置
如果要把自己开发的库文件安装到操作系统中,需要有管理员权限:
(a) 把库文件复制到适当的目录:
可以把自己开发的动态连接库放到/usr/local/lib(或者/usr/lib),或放到其他目录,但不论放在那里,都必须与LIBRARY_PATH的值、LD_LIBRARY_PATH的值相一致。
(b) 修改相关的系统配置文件:
修改/etc/ld.so.conf,然后利用/sbin/ldconfig来完成。
Note:
编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
l -L.:表示要连接的库在当前目录中
l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

静态库(.a)

生成静态库的方法如下:

  • 编译object文件。例如:cc -Wall -c ctest1.c ctest2.c,该命令会生成ctest1.octest2.o(其中-Wall表示编译时输出警告)。
  • 创建库文件。例如:ar -cvq libctest.a ctest1.o ctest2.o。该命令会得到一个libctest.a文件
  • 可以通过ar -t查看.a文件中包含哪些.o。所以,实际上ar就是一个打包命令,类似tar
  • 构建符号表。ranlib libctest.a用于为.a创建符号表。有些ar命令实际上已经集成了ranlib的功能

.a文件与windows下的.lib是相同的概念。

Linux静态库和动态库

在windows平台和linux平台下都大量存在着库。本质上来说库是一种可执行的二进制代码(但不可以独立执行),可以被操作系统载入内存执行。

Linux下的库有两种:静态库和动态库(共享库)。

本文将介绍Linux下静态库和动态库的概念以及相应的创建与使用方法,文章内容为个人学习笔记,欢迎指正。

动态库(.so)

生成动态库的方法如下:

编译object文件时使用-fPIC选项:

gcc -Wall -fPIC -c *.c

这个选项的目的是让编译器生成地址无关(position independent)的代码,这是因为,动态库是在运行期间链接的,变量和函数的偏移量是事先不知道的,需要链接以后根据offset进行地址重定向。

使用-shared链接

gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 *.o

-shared选项是让动态库得以在运行期间被动态链接;-Wl,options是设置传递给ld(链接器)的参数,在上面的例子中,当链接器在链接.o时会执行ld -soname ibctest.so.1

创建软链

上面的命令将最终输出一个动态库libctest.so.1.0,而出于习惯,会创建两个软链:

mv libctest.so.1.0 /opt/lib
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so

libctest.so用于在编译期间使用-lctest让编译器找到动态库,而libctest.so.1用于在运行期间链接

gcc -Wall -I/path/to/include-files -L/path/to/libraries prog.c -lctest -o prog

1 对比

类型 特点
静态库 在编译时被链接到程序中,作为可执行程序的一部分。在程序运行时不再依赖静态库,占用内存大
动态库 在可执行程序运行时载入内存。动态库已经在内存中不需要再次载入

查看依赖

使用ldd命令来查看程序对动态库的依赖。例如:

ldd prog

libctest.so.1 => /opt/lib/libctest.so.1 (0x00002aaaaaaac000)
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003aa4e00000)
/lib64/ld-linux-x86-64.so.2 (0x0000003aa4c00000)

2 静态库

obj文件

obj文件的格式和组成可能是系统差异性的一大体现,比如windows下的PElinux和一些unix下的elfmacosmach-oaix下的xcoff

查看obj文件的符号表信息,可以通过nm objdump readelf等方法。

2.1 概念

静态库指将所有相关的目标文件打包成为一个单独的文件,即静态库文件

静态库以.a结尾,链接器会将程序中使用到的函数的代码从库文件中拷贝到程序中。

由于每个使用静态库的应用程序都需要拷贝所有函数的代码,所以静态链接的文件会比较

在Unix系统中,静态库以一种称为存档(archive)的特殊文件格式存放在磁盘中。

存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置。

运行期间查找动态库

运行期间,系统需要知道到哪里去查找动态库,这是通过/etc/ld.so.conf配置的。ldconfig用于配置运行时动态库查找路径,实际是更新/etc/ld.so.cache。另外一些环境变量也可以影响查找:(Linux/Solaris: LD_LIBRARY_PATH, SGI: LD_LIBRARYN32_PATH, AIX:LIBPATH, Mac OS X: DYLD_LIBRARY_PATH, HP-UX: SHLIB_PATH)

2.2 静态库的创建和使用

动态加载和卸载的库

需要应用程序希望设计成插件化的架构,这就需要可以动态加载和卸载库的机制。与动态链接不同的是,动态加载的意思是,编译期间可以对动态库的存在一无所知,而是在运行期间通过用户程序尝试加载进来的。

通过dlfcn.h中的dlopendlsymdlclose等函数实现此种功能。

另外,使用到dlfcn机制的可执行文件需要使用-rdynamic选项,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里。

2.2.1 环境准备

创建根文件夹staticDemo:

[root@VM_120_243_centos ~]# mkdir staticDemo

创建子文件夹include,用于存放所有头文件:

[root@VM_120_243_centos ~]# cd staticDemo/
[root@VM_120_243_centos staticDemo]# mkdir include

进入include文件夹,编写myLib.h文件,申明printInfo()函数:

[root@VM_120_243_centos staticDemo]# cd include/
[root@VM_120_243_centos include]# vim myLib.h
[root@VM_120_243_centos include]# cat myLib.h 
void printInfo();

进入上层目录,创建lib文件夹,用于存放所有库文件,再其中编写print.c文件,使用了myLib.h中的printInfo()函数:

[root@VM_120_243_centos include]# cd ..
[root@VM_120_243_centos staticDemo]# mkdir lib
[root@VM_120_243_centos staticDemo]# cd lib/
[root@VM_120_243_centos lib]# vim print.c
[root@VM_120_243_centos lib]# cat print.c 
#include <stdio.h>
#include "myLib.h"

void printInfo()
{
    printf("print from print.c file...n");
}

进入上层目录,编写main.c文件,用于测试:

[root@VM_120_243_centos lib]# cd ..
[root@VM_120_243_centos staticDemo]# vim main.c
[root@VM_120_243_centos staticDemo]# cat main.c 
#include <stdio.h>
#include "myLib.h"

int main(void)
{
    printf("print from main.c file...n");
    printInfo();
    return 0;
}

准备工作到此完成,整个目录结构如下:

[root@VM_120_243_centos staticDemo]# tree
.
├── include
│   └── myLib.h
├── lib
│   └── print.c
└── main.c

2 directories, 3 files