linux——动静态库

Posted 努力学习的少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux——动静态库相关的知识,希望对你有一定的参考价值。

目录

动静态的比较

扩展名

编译操作

 执行的状态 

生成静态库

生成动态库

总结


在linux操作系统中,函数库是一个非常重要的的东西,因为很多软件之间都会互相使用彼此提供的函数来使用其特殊的功能。例如

我们在写c语言的时候,但我们要使用printf这个函数时,我们都会包含stdio.h这个库,因为printf的具体实现是放在stdio.h文件里面的。

什么叫做库呢?

函数库其实本质是一堆非包含main的函数的.c文件,通过编译后形成相对应的.o文件,然后将这堆.o文件中所有代码打包在一个文件下,这个文件就是库,也就是说函数库是一堆.o文件的集合。当我们要使用该库的某些功能时,我们只需要将我们的程序和库中的某些.o文件链接起来就可以了。

 函数库根据使用的类型分为两大类,分别是静态库(Static)和动态库(Dynamic).

动静态的比较

扩展名

静态库的通常是扩展名为 libxxx.a, xxx是实际库名称。静态库库的名称是去掉前缀lib和后缀.a。

动态库的通常的扩展名为libxxx.so,xxx是实际的库的名称,动态库的名称是去掉前缀lib,和后缀.so

ldd test 查看test可执行程序所依赖的动态库

 test所依赖的动态库的扩展名是libc.so.6,当我们去掉前缀lib和后缀.so.6时,剩下的c就是我们的库名称,所以test依赖的库是c库。

编译操作

静态库:

这类函数在编译链接的时候的时候是直接将库中所有的二进制代码整合到程序中,所以利用静态函数库编译链接形成的的文件会比较大。

动态库:

动态函数与静态函数库的编译是差异是很大的,动态库在编译的时候,在程序中仅保留一个指针而已。也就是说,动态函数的中的文件并没有将所有的代码整合到程序中,而是该程序在运行时,要使用该函数库的内容时,才通过指针去读取函数库来使用,程序中仅包含动态库的指针,所以该程序文件会比较小。

 执行的状态 

静态库链接:编译成功的可执行文件是可以独立运行的,不需要依赖任何外界的库才能够运行。静态库中的代码是被映射到程序地址空间的代码区上。

动态库链接:动态库链接的可执行程序不能够独立运行,它的运行必须依赖动态库,所以动态库必须存在才行,而且函数库的所在目录也不能被改变,当程序在运行时需要动态库的功能时,程序会去某个路径下去读取动态库,所以动态库不能够随便的删除或者移动,它将会影响很多个程序。

 程序在运行时,当程序需要动态库中的功能时,系统会将动态库加载到内存中,然后将动态库中的代码映射到程序的地址空间的共享区中,也就是说一个动态库在内存中可以被若干个进程给共享着,当有很多个进程的时候,只需要有一份动态库的代码就可以维持这些进程的运行,所以动态库是可以节省内存空间的。当进程需要的时候就去动态库里面找就可以了。所以当动态库从内存中移除的时候,可能会有很多个进程给挂掉。

总结:

静态库链接的可执行程序

缺点:编译形成的可执行程序大,当有多个静态库链接的可执行程序在内存中运行时也会消耗大量的内存空间。

优点:可以独立执行,不需要要求读取其他库的内容才能运行。

动态库链接的可执行程序:

缺点:不能够独立执行,必须依赖动态库,如果没有动态库时,则相关的所有的动态链接的可执行程序都不能运行。

优点:编译链接形成的可执行程序小,当有多个动态库链接形成的可执行程序在内存中运行时,可以节省大量的内存空间,因为只需要加载一份动态库的内容,就能维持相关的可执行程序

生成静态库

既然我们知道静态库的原理后,我们得学会怎样去制作一个自己的静态库。

 我想实现加法功能和减法的功能,然后将它们打包成一个静态库,以后要用的时候,我们直接调用我们写的接口即可。

 过程:

首先我们先需要将add.c和sub.c行成add.o文件和sub.o文件

[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ gcc -c add.c sub.c

然后将我们的add.o文件和sub.o文件给打包形成我们的静态库

[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ ar -rc libmymath.a add.o sub.o

add.o和sub.o文件打包形成我们的libmymath.a静态库文件

这样我们的静态库就创建完了,创建完之后,我们该怎么使用这个静态库呢?

我们给一个程序编译的时候,是需要将库的头文件和库的实现给让我们的编译器能够找到,所以我们先把静态库中包含的头文件和静态库分别放在不同的目录下,以便编译器可以找到它。

我们将所有头文件都放在mathlib下的include的目录下

把.a文件放在mathlib下的lib目录下

#创建lib目录
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ mkdir -p mathlib/lib  
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ mkdir mathlib/include #创建include目录
 #将libmymath.a文件放到lib下
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ cp *.a ./mathlib/lib 
#将add.h sub.h文件放到
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ cp *.h ./mathlib/include include下
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ tree #查看目录结构
.
├── add.c
├── add.h
├── add.o
├── libmymath.a
├── mathlib
│   ├── include
│   │   ├── add.h
│   │   └── sub.h
│   └── lib
│       └── libmymath.a
├── sub.c
├── sub.h
└── sub.o

我们创建一个test.c文件,然后调用add接口。 

 gcc  test.c:编译test.c文件

 我们发现编译器报错找不到add.h文件

所以我们在gcc编译时加上 -I ./mathlib/include

加上-I 路径 是编译时使编译器到指定的路径下寻找头文件 

 此时我们发现我们找不到add的具体实现,因为编译器不知道要去哪个路径下找有add实现的库。

所以我们需要在加上-L ./mathlib/lib 指明编译器到./mathlib/lib的路径下寻找有add实现的库

 加上-L ./mathlib/lib之后,我们发现还是找不到add的具体实现,因为该./mathlib/lib路径下有可能存在很多的静态库,所以当编译器到该路径下时不知道要链接哪一个库,因此我们得再加一个

-l mymath,指明要链接哪一个库(注意-l后面加的是库的名称,而不是库的扩展名)

 那么为什么我们之前用c库里面的函数的时候,我们只需要gcc test.c即可编译完成,不需要加上后面那些指令就可以完成编译呢?

因为我们的c库是放在是放在系统的路径下,当我们编译的时候,编译器会自动去改路径下寻找库的头文件和库的实现。

系统存放头文件路径是/usr/include 

 系统存放库的路径是/usr/lib

所以我们也可以将我们的的头文件放到/usr/include的路径下,把我们的libmymath.a库文件放到/usr/lib的路径下,这样我们的编译器编译的时候就会到这两个路径下去寻找我们的头文件和库文件了。

#将所有的头文件导入到/usr/include这个路径的目录下
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ sudo cp mathlib/include/* /usr/include
#将静态库.a文件导入到/usr/lib这个路径的目录下
[sjp@iZwz97d32td9ocseu9tkn4Z lesson21]$ sudo cp mathlib/lib/*.a  /usr/lib

但是我们编译gcc test.c时还是找不到add的实现。 

  因为编译器还是找不到add的具体实现在哪一个库,所以,需要指明-l要指明链接哪一个库。那为什么之前使用c库的函数就不用指明链接的库文件呢?

因为gcc编译器会自动帮我们去链接c库函数。

生成动态库

知道怎么静态怎么制作后,接下来我们得学习如何制作动态库了。

生成动态库也是需要先将.c文件编译生成.o文件,只是生成.o文件的时候需要带上

gcc  -fpic 

   -fpic选项作用于编译阶段,告诉编译器产生与位置无关代码
    (Position-Independent Code),则产生的代码中,没有绝对
    地址,全部使用相对地址,故而代码可以被加载器加载到内
    存的任意位置,都可以正确的执行
。这正是共享库所要求的,
    共享库被加载时,在内存的位置不是固定的

 然后将add.o和sub.o文将进行打包形成我们的动态库。

gcc -shared -o libmymath add.o sub.o

        

 与静态库一样,可以创建一个目录,然后将动态库中的头文件和库的实现放在这个目录中,方便我们查找使用。

 我们将动态库制作完之后,我们该怎么用它呢?

编译链接的时候跟我们的静态链接是一样的,也是需要带 -I -L -l选项, 

 我们在ldd查看a.out相关的动态库,发现他将与我们制作的动态库libmymath.so相关联。

 此时我的a.out是可以运行起来的,如果你的可执行程序运行不起来,此时是因为我们的程序加载到内存的时候,系统找不到该动态库,使库不能够被运行。我们之前写的-I -L -l是为了让编译器能够找到我们的动态库的路径,而运行我们的程序时,是需要让操作系统找到我们的动态库。 

程序编译链接时要找动态库:

可执行程序运行时要找动态库: 

 

为了能够使我们的程序能够运行起来,我们就得使系统找到该动态库,然后让该动态库加载到内存中,使动态库运行起来,我们可以导出一个环境变量LD_LIBRARY_PATH。

操作系统会默认到该环境变量下的路径查找动态库。

查看该环境变量 :

 将我们的动态库的路径导入到该环境变量里去,这样我们的程序运行时,系统就可以找到该环境变量。

 这样我们的系统就能够自动的到/home/sjp/lesson21/dynamic/math.so/lib这个路径下找我们的mymath这个动态库了。

总结:

制作静态库过程:

制作动态库的过程: 

 

 结语:

好啦,今天的内容就分享到这里了,喜欢的小伙伴们,麻烦你们给我个三连,谢谢

以上是关于linux——动静态库的主要内容,如果未能解决你的问题,请参考以下文章

linux入门动静态库

linux入门动静态库

Linux动静态库以及动静态链接

Linux编译过程与动静态库制作

Linux下的动态库和静态库详解

Linux篇第十篇——动静态库(动静态库的介绍+动静态库的打包与使用)