Linux下动静态库的打包与使用指南(C/C++)
作者:谜一样的男人1 发布时间:2023-06-21 20:22:11
前言
为什么用动静态库
我们在实际开发中,经常要使用别人已经实现好的功能,这是为了开发效率和鲁棒性(健壮性);因为那些功能都是顶尖的工程师已经写好的,并且已经践行多年的代码。
那么如何使用他人开发的功能呢?
1.库: 包括静态库与动态库。
2.开源代码。
3.基本的网络功能调用,比如各种网络接口、语音识别等等。
这其中,我们将详细介绍静态库和动态库:
为什么在实际工作中一般将源码打包成动态静态库来给不同的人使用?
知识就是财富,你只是给某些人用一下你写的业务功能,并不想暴露源码的逻辑,那就打包成.obj目标文件甩给他用;
你也可以自己给自己封装库,因为你自己编译生成了.obj目标文件,是已经编译完成了的,只需要把这个库对应的头文件引入新的程序中,.obj文件放入库路径,这样新的程序链接器就可以直接把功能链接起来,方便部署使用,省去了反复编译的麻烦;
动态链接与静态链接
一般情况下,为了更好的支持开发,第三方库或者是语言库都必须提供静态库和动态库(eg:C C++等官方库),这是方便程序员根据需求功能进行可执行文件的生成;
动态链接使用动态库,而静态链接使用静态库。
一般来说,我们gcc编译默认是动态链接的而如果加上-static选项,那么生成的可执行文件将为静态生成;
底层优缺点
动态链接文件信息:
静态链接文件信息:
可以明显发现动态链接的文件大小明显要比静态链接的文件大小要小多了
这是因为动态链接是当程序执行到调用接口时,编译器再去特定路径查找目标接口的可执行文件,直接进行计算;
静态链接比较暴力,链接时候直接将目标接口的二进制代码全部链接到原文件中去,这也就是静态链接生成的文件这么大的原因了;(毕竟把二进制代码copy过来了)
但是这些都是相对的,有优点就有缺点:
万一动态库路径中的库丢失损坏 ,动态链接的程序到目标位置了,过来用的时候肯定出错了;
静态链接因为编译的时候吧二进制代码考过去了,不依赖原生库,即便原库代码丢失也没事;
小结
Linux下的动静态库
linux下库的命名格式一般为:
静态库: lib+库的名字+.a eg:c标准库为 libc.a
动态库: lib+库的名字+.so
静态库是指程序在编译链接的时候把库的二进制可执行代码链接到可执行文件中。程序运行的时候将不再需要静态库。
而动态库则是指程序在运行的时候才去启动指定位置的动态库的代码,使其加载到内存共享区中,多个程序共享使用库的二进制代码, 不用拉到本文件中来。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表(头文件),而不是外部函数所在目标文件(.o)的整个机器码
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking),也就是说,动态链接是在需要调用接口时才会去将所用接口的二进制代码拷贝到内存中。
当一个库多文件使用时,动态库只有一份,所以可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。–>操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
这里需要提一下的是,我们之前所提过的进程地址空间中有一个共享区,而一般动态库的代码就映射在共享区,所有进程都共享着动态库的代码。
动静态库的对比
动态库被加载在内存中,可以供多个使用库的程序共享映射到自己的虚拟地址空间使用,因此可以减少页面交换以及降低内存中代码冗余,并且因为与源程序模块分离,因此开发模式比较好。
而加载动态库的程序运行速度相对较慢,因为动态库运行时加载,映射到虚拟地址空间后需要重新根据映射起始地址计算函数/变量地址。
静态库直接把二进制代码链接过来,与动态库的使用恰好相反,其运行速度相对较快,但消耗资源较多。
打包静态库
库函数源文件:
//file1: Add.c
#include<stdio.h>
int Add(int a,int b)
{
return a+b;
}
//file2: Sub.c
#include<stdio.h>
int Sub(int a,int b)
{
return a-b;
}
生成静态库需要先生成目标文件(.o)再进行打包,故先编写相应的源文件再将其编译成目标文件:
//生成两个二进制目标文件
[root@VM-8-15-centos fighting]# gcc -c Sub.c -o Sub.o
[root@VM-8-15-centos fighting]# gcc -c Add.c -o Add.o
此时的add.o和sub.o文件是已经编译好但还没有链接的两个文件;
此时再用 ar命令,归档工具将其打包成静态库:
//将这俩二进制目标文件打包成静态库
[lyl@VM-4-3-centos 2022-3-14]$ ar -rc libmycal.a Add.o Sub.o //`rc`表示(replace and create)
查看静态库
//查看静态库的目录列表
[root@VM-8-15-centos fighting]# ar -tv libmycal.a `tv`表示(列出静态库中文件 and verbose详细信息)
rw-r--r-- 0/0 936 Jan 24 16:57 2023 Add.o
rw-r--r-- 0/0 1240 Jan 24 16:57 2023 Sub.o
使用静态库
将库的头文件和静态库都放到指定lib目录下:
调用我们的库接口代码:
#include <stdio.h>
#include "add.h"
#include "sub.h"
int main()
{
int a = 10;
int b = 20;
printf("a+b:%d\n", Add(a, b));
printf("a-b:%d\n", Sub(a, b));
return 0;
}
编译:
发现报错: 这是因为gcc编译时去链接库和头文件,是去默认路径以及当前源文件路径下寻找;
//gcc 寻找的默认头文件路径:不建议污染原生库
/usr/include
//gcc 寻找的默认库文件路径:
/lib , lib64 ......等
而我们将静态库打包到lib目录下,gcc编译时就找不到我们的库了,因此我们编译的时候,需要指定一些选项,并且带上路径./lib;
因此,正确链接的指令为:
gcc -o test test.c -I ./lib -L ./lib -lmycal -static
-I(大写i) + 指定路径:告知gcc除了默认路径之外,还要去寻找这个指定路径的头文件。
-L + 指定路径:除默认库路径以外,需要寻找这个指定路径的库
-l(小写L)+ 库名称:表示要具体链接的是哪一个库;(因为路径下库可能不止一个)
-static 选择使用静态库的静态链接
由此,我们就静态链接生成了一个可执行文件test,运行test程序结果如下:
此时我们删除静态库,发现照样可以运行,因为静态库中Add和Sub的二进制代码已经被链接入test程序中了,不怕原生库没了!
可见,有时候编译选项多而杂,难记,特别是文件一多,写的很麻烦,介绍一个camke构建项目的工具,C/C++开发人员必会技能;文章链接
打包动态库
类似与打包静态库使用的ar归档工具,动态库也有自己的语法,我们将生成动态库的依赖关系及方法写进自动化构建工具(Makefile)中::
显然手动写Makefile和上面手动打包静态库一样,麻烦很多,我的评价是,直接cmake起飞;
注意:
由于动态库在内存中是可加载的,它可能在内存中的任意位置,也可能被映射到进程地址空间的每个区域,所以为了保证库当中的代码执行不会出错,也就是要保证库中的代码是与位置无关的,因此生成.o文件时需要带上-fPIC选项表示生成与位置无关码。
这里由于在依赖关系中已经点明了要生成的目标文件,故不带上$@也可以
打包动态库不是像静态库一样先gcc -o再使用ar(归档工具);
而是用gcc 带上-shared选项表示生成共享动态库格式,这也体现了动态库代码映射在共享区的特点
编写好Makefile之后 make指令构建动态库 libmycal.so:
使用动态库
和静态库一样,我们把头文件和.so库文件放入lib目录,gcc的时候带上选项;
gcc -o test test.c -I ./lib -L ./lib -l mycal //因为是动态链接 所以不用带-static了
然后编译过了,运行程序时发现有问题,打不开动态库?:
既然编译都声称可执行程序了,此时的可执行程序是没问题的,因此已经与编译过程无关了;
那么这属于运行问题,其实运行时系统也会去默认路径下找到我们所使用的动态库,但在默认路径下没有我们的库。
这里解决方法有多种,但我倾向于推荐下面这一种:
修改环境变量LD_LIBRARY_PATH
,将动态库所在路径.lib添加到该环境变量中,这样程序在运行时系统就能够找到动态库,从而运行成功。
当然,还可以拷贝我们的.so文件到系统共享库路径下, 一般指/usr/lib;
但是这可能会污染系统原生的库,一般不推荐这样做。
还有一种方法,在我们的系统下有**/etc/ld.so.conf.d/**这个路径:
我们可以在这个路径下制造自己的.conf,然后再将自己的库路径写进这个conf中;
但是还是有点污染了原生库,不建议;
小结
linux打包使用静态库:
接口的.c源文件–>.o目标二进制文件–>ar rc(归档工具)进行打包成.a静态库,编译程序使用时带上-static;(注意带上头文件寻找路径选项)
linux打包使用动态库:
接口的.c源文件–>.o目标二进制文件(需带上-fPIC 与位置无关)–>-shard 打包动态库;(注意带上头文件寻找路径选项)+(注意添加动态库寻找路径)
win下打包动静态库
比如通过VS2019打包,由于是可视化界面方便操作,不再赘述,参考下方文章;
参考文章
来源:https://blog.csdn.net/wtl666_6/article/details/128757570


猜你喜欢
- 本文实例为大家分享了Android实现简单banner轮播图的具体代码,供大家参考,具体内容如下说明:想玩一个简单的轮播图效果
- 一.什么是CASCAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期
- Android自定义View实现等级滑动条的实例实现效果图:思路: 首先绘制直线,然后等分直线绘制点; 绘制点的时候把X值存到集
- volatile变量volatile是Java的关键词,我们可以用它来修饰变量或者方法。为什么要使用volatilevolatile的典型用
- 1. 简介zookeeper是一个开源的分布式协调服务, 提供分布式数据一致性解决方案,分布式应用程序可以实现数据统一配置管理、统一命名服务
- 一、Stream类概述在.NET Framework中,文件和流是有区别的。文件是存储在磁盘上的数据集,它具有名称和相应的路径。当打开一个文
- Javaweb开发环境的配置也是比较繁琐的一件事情,虽然理论上使用记事本,完全可以写出一个Javaweb工程,但是在团队大型开发的Javaw
- 静态代理第一种实现(基于接口):1》接口public interface Hello { void say(String msg);}2》目
- 一、ArrayList是什么ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元
- 【前言】AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统
- 前段时间摸索了java调用matlab东西,不说学的有多深,也算有结果了,达到目的了。也即用java程序可以调用matlab中函数了。&nb
- 前面有文章曾经地介绍过MediaPlayer的基本用法,这里就更加深入地讲解MediaPlayer的在线播放功能。本文主要实现MediaPl
- 注意,本文所说的断点续传特指 HTTP 协议中的断点续传。本文主要聊聊思路和关键代码,更多细节请参考本文附带的 demo。工作原理HTTP
- 本文实例为大家分享了Unity3D撤回命令功能开发,供大家参考,具体内容如下在类似操作考核的项目中我们经常会遇到回到上一步的需求。所以我们有
- 1、简介应客户要求为了是特殊定制的系统更具安全,系统ROM需要使用自己定义的签名,还有一些特殊的场景也会更改系统的签名比如在过cts认证测试
- 麦洛开通博客以来,有一段时间没有更新博文了.主要是麦洛这段时间因项目开发实在太忙了.今天周六还在公司加班,苦逼程序猿都是这样生活的.今天在做
- 要说this和super就不得不说Java的封装和继承了,首先说封装,这是一种思想,算不上一种技术,核心思想就是将对象的同一行为和状态看成是
- 前面讲了 Spock框架Mock对象方法经验总结一、静态方法Mock静态方法我们使用PowerMock结合Mockito的方案,首先在测试类
- Hibernate中有HQL查询语法。但我们用得比较熟的还是数SQL语句,那么应该怎么来让Hibernate支持SQL呢?这个不用我们去考虑
- 本文实例讲述了Java常用HASH算法。分享给大家供大家参考,具体如下:/*** Hash算法大全<br>* 推荐使用FNV1算