Linux系统编程——超级详细讲解静态库、动态库的创建、打包和使用
目录
一、前言
在Linux环境下,我们使用gcc编译连接代码时,可以分为静态链接和动态链接。
- 静态链接即在编译链接时,将代码所使用到的静态库文件代码全部加入到可执行文件中(拷贝实质上是将库文件的代码展开拷贝至我们的可执行程序的代码段。),这样做的缺点是可执行文件会生成的比较大,优点是此时再运行可执行文件就不需要再查找库了。静态库文件一般以 .a 结尾。
- 动态库链接不会在编译时将动态库文件的代码加入到可执行文件中,而是在可执行文件运行时,去查找所需的动态库,并将其加载到相应的进程中,并且不同的进程可以共享这些动态库,动态库文件一般以 .so 结尾。
什么是库呢?
简单来说就是由别人所写的一些函数、方法等代码经过归档整合之后发布出来以供大家使用的东西就称为库。每个人都可以生成库,然后发布出去以供别人使用。Linux库文件的命名通常以lib为前缀,后面跟库名称和文件扩展名。例如,一个名为math的静态库文件可能命名为libmath.a,而对应的动态库文件则可能命名为libmath.so。如果动态库有多个版本,版本号也会包含在文件名中,如libmath.so.1。
二、静态库的创建
我们自己来实现一个库的创建,在创建自己的库的时候不需要携带main函数。但是我们这里为了方便演示就加上一个。
Tips:在vim想要实现打开其他文件实现分屏操作,可以在vim的底行模式下输入:vs 文件名
makefile文件,在vim文件里让其省略中间步骤直接生成可执行文件
运行结果:
我们在 makefile 这一篇文章中讲到过,可执行文件的编译过程,现在我们添加选项将编译和链接分开,先使用 -c 选项将代码先编译成目标文件先不做链接
可以看到已经生成了两个目标文件。我们知道下一步就该将这些.o目标文件链接起来生成可执行程序了。那如果我们此时将这些.o目标文件全部打包交给另一个人,那么他能直接使用这些目标文件生成可执行程序吗?答案是可以的。
下面做个实验:我们创建一个新的目录,并只将这两个目标文件移动过去(.c文件),创建一个新的main函数,并在main()函数中再使用这两个函数
如果不把.h文件也放过来的话,在编译源文件的时候,第一步是要预处理,需要头文件展开,但是找不到头文件就会报错。
可以看到,即使没有指定编译 .c 文件, 只将 .o 文件链接起来,就可以生成一个可执行文件。
这个过程,其实就是静态链接的过程。但 .o 文件,并不是静态库,而静态库,则是由这些 .o 文件生成的。
- 所以如果我们不想给对方提供我们的源代码,就可以将我们的 .o 可重定位目标二进制文件给对方,让对方用他的代码来进行链接。
- 所以我们将来就可以给对方提供 .o (方法的实现) .h(都有什么方法),但是如果我们有着许多的.o文件,我们难道也需要将这么多的.o 文件一个个发过去吗?这也太麻烦了,所以我们就将所有的 .o 文件打一个包,给对方提供一个库文件即可。
- 打包的本质就是将所有的 .o 文件形成一个文件,这个文件就是库,而根据打包工具和打包方式的不同才分为静态库和动态库。
1、创建静态库
上面演示了什么是静态链接,也知道了静态库其实就是由源文件编译生成的 .o目标文件生成的。
创建静态库的命令是: ar -rc 库名 .o文件 .o文件 .......
ar可以看作是归档的意思:archive, -rc则是 replace和 create覆盖或创建
查看库文件
2、发布静态库
我们在上面已经创建成功了库文件,那么该如何使用呢?是直接将库文件拷贝过去就可以了吗?
是不行的。
我们能在Linux下编写C语言就是因为有头文件和库文件,所以我们除了给我们的库文件还需要给我们的头文件(得用我们的头文件中包含的方法)。
所以我们交付库的时候,是将库文件 .a .so + 匹配的头文件都交给别人
所以为了方便我们进行交付,做一款简单的软件,将我们写好的库直接交付给别人去用,我们再对makefile进行修改,makefile文件如下:
结果如下:
接下来我们将这个目录文件压缩打包,这样我们将来就可以将这个压缩文件放在yum的资源或者放在网站之中,别人需要的时候就可以直接下载。
我们省略上传的过程,直接将这个压缩包拷贝到其他目录下解压来代替下载安装,如下:
我们已经“下载”完,下来安装是怎么一回事?实际上我们的安装本质上还是拷贝,就是将它所对应的可执行程序拷贝到系统指定的能找到的路径下。
3、使用静态库
下来我们直接在命令行中 直接编译
可以看到直接报错,找不到头文件,明明头文件已经解压了且在当前的路径下了为什么还是找不到呢?这是因为gcc是一个编译器,在编译器搜索头文件的时候,默认一是在当前路径下搜索,而是在系统头文件下搜索。这里是因为头文件的路径太深了,必须要和源代码处在同一级路径下才可以找到。
所以接下来我们可以给编译器指定头文件目录:
但是它又报错,可以看到报错里面有.o ,判断它是链接出的问题,即找不到库文件,在链接的时候必须要指明库文件的路径在哪里。
指定头文件路径 指定库文件路径 指定库名称
所以说我们在链接第三方库的时候必须要指明库的名称。强调一下库的名称不是libxxxxx.a,库的名称是xxxxx。那么为什么我们平常在使用gcc、g++进行编译的时候为什么不用指定库的名称呢?因为我们默认使用的标准库时,gcc、g++的cc ++就已经表明了使用的是C的还是C++的标准库。
静态链接?动态链接?
接下来我们查看可执行文件的信息
可以看到明明我们使用的是静态库,怎么这里显示的是动态链接。 我们再查看一下该可执行文件依赖的共享库。使用ldd命令
ldd
是 Linux 系统中的一个命令,用于打印共享库依赖关系。当你运行一个可执行文件时,它可能依赖于多个共享库(动态链接库)。ldd
命令可以帮助你查看这些依赖的共享库,并显示它们的位置和版本信息。
可以看到该可执行文件依赖的库里怎么没有我们创建的静态库,全是.so结尾的动态库呢?
gcc默认是动态链接。形成一个可执行程序,可能不仅仅是依赖一个库。就像写一个程序可能需要链接很多库。
对于单个库来说,虽然gcc默认是动态链接的(gcc建议行为),对于特定的一个库,究竟是 动态链接还是静态链接,取决于你提供的是动态库还是静态库。如果此时只有一个静态库,编译器也没办法,它只能以静态方式将该库拷贝到可执行程序中。
对于多个库来说,还是需要一个个链接的,这就回到了上面的一个库的情况。只要有一个动态库那他就是动态链接的。
所以这就是为什么上图显示的动态链接,因为它还链接了C语言的库,而我们所创建的库是静态的,gcc虽然是默认动态链接的,但是看到我们的库也没办法,只能以静态方式将我们的库拷贝一份放在可执行程序中。
安装到系统头文件目录下
上面我们提到了静态库的使用,但是我们每次要用这个库的时候还要带各种选项还得手动通过路径找到它,这未免太过于麻烦,我们也想让它和C的标准库一样使用,所以接下来,我们将其“安装”到系统的头文件目录下:
这就是我们平常的软件的安装过程,安装的本质其实就是拷贝
这个时候我们再编译
需要注意的是,我们在编译的时候还需要指定我们的库名称,因为我们的库是第三方库,使用第三方库必须要指明库名称,C和C++的库名称是不需要的。
上面所提到的安装方法是不推荐的,因为这会污染我们的系统,所以在实验结束之后最好删除掉,删除的时候千万要注意,不要删错了系统的文件,不然很麻烦。
三、动态库的创建
1、创建、打包动态库
- 动静态库都是由源码编译出的.o目标文件生成的,但是生成动态库和静态库的.o目标文件是有区别的。
- gcc -c 生成的目标文件, 只能用于静态库的创建, 不能用于动态库的创建。
- 创建动态库所需要的.o文件是通过 gcc -fPIC -c编译生成的。
- 生成静态库使用的是系统提供的ar归档工具,而生成动态库则需要使用gcc,gcc -shared -o,shared表示生成共享库格式。
生成动态库的makefile如下,跟静态库的主要区别就在下面几个地方
运行结果:
什么是fPIC选项?
PIC(Position Independent Code), 位置无关代码。
我们知道, 目标文件链接为可执行程序的过程, 其实是将目标文件的代码加载到程序中, 并对代码分配地址的过程.
在使用gcc -c(不加fPIC时)编译生成的目标文件是与位置有关的代码. 在链接时, 会将代码加载到程序中并且分配在进程中的绝对地址. 即在调用静态库函数时, 会调用这个本来就表示函数的绝对地址. 既然是绝对地址, 那么程序加载到内存中时, 这部分代码是不能加载到随意的位置的, 只能加载到进程的固定位置, 供操作系统实际调用。
而我们在添加fPIC后,编译生成的目标文件是与位置无关的代码. 在链接时, 会将代码以一个相对地址的形式加载到程序中, 即在调用动态库函数时, 会通过调用程序中的的一个表示函数位置的相对地址, 这个地址并不直接表示函数, 而是通过这个地址可以找到内存中加载的代码. 既然是相对的地址, 也就意味着其实动态库代码可以加载到内存的任意位置.
2、使用动态库
接下来就像前面的静态库的操作一样,我们将mylib这个目录移动到新的目录下,进行编译看看实验结果:
可以看到我们明明都指定了头文件、库文件路径、库文件名称可是为什么还是报错说找不到我们的库文件,这是怎么一回事呢?
这是因为我们指明的时候是向gcc编译器指明的。在静态链接的时候,编译的时候,我们是直接将库文件拷贝到可执行程序中来的,后面就不需要再寻找库文件了。但是动态链接不一样,它没有将库函数拷贝到可执行程序中,它是在程序运行的时候才慢慢加载到可执行程序中的,在gcc编译完成后,和gcc就没有关系了,所以在程序运行起来的时候,OS和shell也是需要知道库在哪里的,不然加载不进来。这就是为什么上面报错,因为我们的库还没有在系统路径下,OS和shell不知道库在哪里。
3、库的搜索路径
那么我们该如何告诉OS和shell我们的库在哪里呢?
1)环境变量法
在我们的程序运行的时候,OS或shell在运行时,除了系统默认路径下,他也会在其他地方进行库文件的搜索。第一个就是在环境变量中搜索,LIBRARY_PATH,所以我们的第一个方法就是将库文件所在的路径添加到该环境变量中。
当然我们也知道当前这种方法只是临时更改环境变量,当我们退出登录之后重新再运行的话又会报错,想要不出问题我们可以修改环境变量相关的配置文件。
2) 配置文件法
/etc/ld.so.conf.d/
在这个系统路径下,保存的是搜索动态库的配置文件,这里面的这些配置文件的內容就是一个动态库所在的路径。
我们可以在这里添加一个类似的文件,让shell知道向哪里查询动态库。照葫芦画瓢,添加一个指定动态库路径的配置文件,文件名可以随便写的。
我们在创建自己的文件后,将库文件所在的路径添加到里面的时候,会出现无法写入的情况,这时候需要用chmod更改一下该文件的权限。
这种方法下我们在退出登录的时候,再次登陆执行可执行文件的时候,还是会成功的。它是永久存在的。
Tips:我们在测试完之后,删除掉配置文件后,可以用 ldconfig 来取消配置文件的加载。
3)添加软连接
1、在当前路径下建立软链接
即在我当前目录下就创建了一个 我自己使用的一个软链接,说明在搜索动态库的时候也可以默认在当前路径下进行搜索。
Tip:使用unlink 目标文件 取消链接
2、在系统库文件路径下建立软链接
4)在系统库目录下添加使用的动态库
把动态库拷贝到/lib64下,就是将动态库安装到系统中,系统的库目录,不仅仅是只给gcc提供库的查找路径的,而是给系统中的所有进程的,所以 在系统库目录下添加动态库, 所有进程就可以找到。这种方法我们就不做实验
原文地址:https://blog.csdn.net/newbie5277/article/details/144301165
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!