当前位置: 首页 > news >正文

Linux 静态库和动态库

        不管是Linux还是Windows中的库文件其本质和工作模式都是相同的, 只不过在不同的平台上库对应的文件格式和文件后缀不同。程序中调用的库有两种 静态库和动态库,不管是哪种库文件本质是还是源文件,只不过是二进制格式只有计算机能够识别,作为一个普通人就无能为力了。

        在项目中使用库一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密,毕竟不是所有人都想把自己编写的程序开源出来。

        当我们拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的API函数的声明,也就是头文件,把这些都添加到项目中,就可以快乐的写代码了。

1. 静态库

        在Linux中静态库由程序 ar 生成,现在静态库已经不像之前那么普遍了,这主要是由于程序都在使用动态库。关于静态库的命名规则如下:

  • 在Linux中静态库以lib作为前缀, 以.a作为后缀, 中间是库的名字自己指定即可, 即: libxxx.a
  • 在Windows中静态库一般以lib作为前缀, 以lib作为后缀, 中间是库的名字需要自己指定, 即: libxxx.lib

1.1 生成静态链接库

        生成静态库,需要先对源文件进行汇编操作 (使用参数 -c) 得到二进制格式的目标文件 (.o 格式), 然后在通过 ar工具将目标文件打包就可以得到静态库文件了 (libxxx.a)。

使用ar工具创建静态库的时候需要三个参数:

  • 参数c:创建一个库,不管库是否存在,都将创建。
  • 参数s:创建目标文件索引,这在创建较大的库时能加快时间。
  • 参数r:在库中插入模块(替换)。默认新的成员添加在库的结尾处,如果模块名已经在库中存在,则替换同名的模块。

生成静态链接库的具体步骤如下:

        1.需要将源文件进行汇编, 得到 .o 文件, 需要使用参数 -c        

# 执行如下操作, 默认生成二进制的 .o 文件
# -c 参数位置没有要求
$ gcc 源文件(*.c) -c	

        2.将得到的 .o 进行打包, 得到静态库

$ ar rcs 静态库的名字(libxxx.a) 原材料(*.o)

        3.发布静态库

# 发布静态库1. 提供头文件 **.h2. 提供制作出来的静态库 libxxx.a

1.2 静态库制作举例

1.2.1 准备测试程序

在某个目录中有如下的源文件, 用来实现一个简单的计算器:

# 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
# main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去
.
├── add.c
├── div.c
├── include
│   └── head.h
├── main.c
├── mult.c
└── sub.c

加法计算源文件 add.c:

#include <stdio.h>
#include "head.h"int add(int a, int b)
{return a+b;
}

减法计算源文件 sub.c:

#include <stdio.h>
#include "head.h"int subtract(int a, int b)
{return a-b;
}

乘法计算源文件 mult.c:

#include <stdio.h>
#include "head.h"int multiply(int a, int b)
{return a*b;
}

减法计算的源文件 div.c

#include <stdio.h>
#include "head.h"double divide(int a, int b)
{return (double)a/b;
}

头文件 head.h

#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);
#endif

测试文件main.c

#include <stdio.h>
#include "head.h"int main()
{int a = 20;int b = 12;printf("a = %d, b = %d\n", a, b);printf("a + b = %d\n", add(a, b));printf("a - b = %d\n", subtract(a, b));printf("a * b = %d\n", multiply(a, b));printf("a / b = %f\n", divide(a, b));return 0;
}

1.2.2 生成静态库

        第一步: 将源文件add.c, div.c, mult.c, sub.c 进行汇编, 得到二进制目标文件 add.o, div.o, mult.o, sub.o

# 1. 生成.o
$ gcc add.c div.c mult.c sub.c -c
sub.c:2:18: fatal error: head.h: No such file or directory
compilation terminated.# 提示头文件找不到, 添加参数 -I 重新头文件路径即可
$ gcc add.c div.c mult.c sub.c -c -I ./include/# 查看目标文件是否已经生成
$ tree
.
├── add.c
├── add.o            # 目标文件
├── div.c
├── div.o            # 目标文件
├── include
│   └── head.h
├── main.c
├── mult.c
├── mult.o           # 目标文件
├── sub.c
└── sub.o            # 目标文件

第二步: 将生成的目标文件通过 ar工具打包生成静态库

# 2. 将生成的目标文件 .o 打包成静态库
$ ar rcs libcalc.a a.o b.o c.o    # a.o b.o c.o在同一个目录中可以写成 *.o# 查看目录中的文件
$ tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│   └── `head.h  ===> 和静态库一并发布
├── `libcalc.a   ===> 生成的静态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

第三步: 将生成的的静态库 libcalc.a和库对应的头文件head.h一并发布给使用者就可以了

# 3. 发布静态库1. head.h    => 函数声明2. libcalc.a => 函数定义(二进制格式)

1.3 静态库的使用

        当我们得到了一个可用的静态库之后, 需要将其放到一个目录中, 然后根据得到的头文件编写测试代码, 对静态库中的函数进行调用。

# 1. 首先拿到了发布的静态库`head.h` 和 `libcalc.a`# 2. 将静态库, 头文件, 测试程序放到一个目录中准备进行测试
.
├── head.h          # 函数声明
├── libcalc.a       # 函数定义(二进制格式)
└── main.c          # 函数测试

编译测试程序, 得到可执行文件。

# 3. 编译测试程序 main.c
$ gcc main.c -o app
/tmp/ccR7Fk49.o: In function `main':
main.c:(.text+0x38): undefined reference to `add'
main.c:(.text+0x58): undefined reference to `subtract'
main.c:(.text+0x78): undefined reference to `multiply'
main.c:(.text+0x98): undefined reference to `divide'
collect2: error: ld returned 1 exit status

上述错误分析:

        编译的源文件中包含了头文件 head.h, 这个头文件中声明的函数对应的定义(也就是函数体实现)在静态库中,程序在编译的时候没有找到函数实现,因此提示 undefined reference to xxxx。

解决方案:在编译的时将静态库的路径和名字都指定出来

  • -L: 指定库所在的目录(相对或者绝对路径)
  • -l: 指定库的名字, 需要掐头(lib)去尾(.a) 剩下的才是需要的静态库的名字
# 4. 编译的时候指定库信息-L: 指定库所在的目录(相对或者绝对路径)-l: 指定库的名字, 掐头(lib)去尾(.a) ==> calc
# -L -l, 参数和参数值之间可以有空格, 也可以没有  -L./ -lcalc
$ gcc main.c -o app -L ./ -l calc# 查看目录信息, 发现可执行程序已经生成了
$ tree
.
├── app   		# 生成的可执行程序
├── head.h
├── libcalc.a
└── main.c

2. 动态库

        动态链接库是程序运行时加载的库,当动态链接库正确部署之后,运行的多个程序可以使用同一个加载到内存中的动态库,因此在Linux中动态链接库也可称之为共享库。

        动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址使用的是相对地址(静态库中使用的是绝对地址),其真实地址是在应用程序加载动态库时形成的。

关于动态库的命名规则如下:

  • 在Linux中动态库以lib作为前缀, 以.so作为后缀, 中间是库的名字自己指定即可, 即: libxxx.so
  • 在Windows中动态库一般以lib作为前缀, 以dll作为后缀, 中间是库的名字需要自己指定, 即: libxxx.dll

2.1 生成动态链接库

        生成动态链接库是直接使用gcc命令并且需要添加-fPIC(-fpic) 以及-shared 参数。

  • -fPIC 或 -fpic 参数的作用是使得 gcc 生成的代码是与位置无关的,也就是使用相对位置。
  • -shared参数的作用是告诉编译器生成一个动态链接库。

生成动态链接库的具体步骤如下:

        1、将源文件进行汇编操作, 需要使用参数 -c, 还需要添加额外参数 -fpic / -fPIC

# 得到若干个 .o文件
$ gcc 源文件(*.c) -c -fpic

       2、 将得到的.o文件打包成动态库, 还是使用gcc, 使用参数 -shared 指定生成动态库(位置没有要求)

$ gcc -shared 与位置无关的目标文件(*.o) -o 动态库(libxxx.so)

        3、发布动态库和头文件

# 发布1. 提供头文件: xxx.h2. 提供动态库: libxxx.so

2.2 动态库制作举例

        在此还是以上面制作静态库使用的实例代码为例来制作动态库, 代码目录如下:

# 举例, 示例目录如下:
# 目录结构 add.c div.c mult.c sub.c -> 算法的源文件, 函数声明在头文件 head.h
# main.c中是对接口的测试程序, 制作库的时候不需要将 main.c 算进去
.
├── add.c
├── div.c
├── include
│   └── head.h
├── main.c
├── mult.c
└── sub.c

第一步: 使用gcc将源文件进行汇编(参数-c), 生成与位置无关的目标文件, 需要使用参数 -fpic或者-fPIC

# 1. 将.c汇编得到.o, 需要额外的参数 -fpic/-fPIC
$ gcc add.c div.c mult.c sub.c -c -fpic -I ./include/# 查看目录文件信息, 检查是否生成了目标文件
$ tree
.
├── add.c
├── add.o                # 生成的目标文件
├── div.c
├── div.o                # 生成的目标文件
├── include
│   └── head.h
├── main.c
├── mult.c
├── mult.o               # 生成的目标文件
├── sub.c
└── sub.o                # 生成的目标文件

第二步: 使用gcc将得到的目标文件打包生成动态库, 需要使用参数 -shared

# 2. 将得到 .o 打包成动态库, 使用gcc , 参数 -shared
$ gcc -shared add.o div.o mult.o sub.o -o libcalc.so  # 检查目录中是否生成了动态库
$ tree
.
├── add.c
├── add.o
├── div.c
├── div.o
├── include
│   └── `head.h   ===> 和动态库一起发布
├── `libcalc.so   ===> 生成的动态库
├── main.c
├── mult.c
├── mult.o
├── sub.c
└── sub.o

第三步: 发布生成的动态库和相关的头文件

# 3. 发布库文件和头文件1. head.h2. libcalc.so

2.3 动态库的使用

        当我们得到了一个可用的动态库之后, 需要将其放到一个目录中, 然后根据得到的头文件编写测试代码, 对动态库中的函数进行调用。

# 1. 拿到发布的动态库`head.h   libcalc.so
# 2. 基于头文件编写测试程序, 测试动态库中提供的接口是否可用`main.c`
# 示例目录:
.
├── head.h          ==> 函数声明
├── libcalc.so      ==> 函数定义
└── main.c          ==> 函数测试

编译测试程序

# 3. 编译测试程序
$ gcc main.c -o app
/tmp/ccwlUpVy.o: In function `main':
main.c:(.text+0x38): undefined reference to `add'
main.c:(.text+0x58): undefined reference to `subtract'
main.c:(.text+0x78): undefined reference to `multiply'
main.c:(.text+0x98): undefined reference to `divide'
collect2: error: ld returned 1 exit status

错误原因:

  • 和使用静态库一样, 在编译的时候需要指定库相关的信息: 库的路径 -L和 库的名字 -l

添加库信息相关参数, 重新编译测试代码:

# 在编译的时候指定动态库相关的信息: 库的路径 -L, 库的名字 -l
$ gcc main.c -o app -L./ -lcalc# 查看是否生成了可执行程序
$ tree
.
├── app 			# 生成的可执行程序
├── head.h
├── libcalc.so
└── main.c# 执行生成的可执行程序, 错误提示 ==> 可执行程序执行的时候找不到动态库
$ ./app 
./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

3. 优缺点

3.1 静态库

优点:

  • 静态库被打包到应用程序中加载速度快
  • 发布程序无需提供静态库,移植方便

缺点:

  • 相同的库文件数据可能在内存中被加载多份, 消耗系统资源,浪费内存
  • 库文件更新需要重新编译项目文件, 生成新的可执行程序, 浪费时间。

3.2 动态库

优点:

  • 可实现不同进程间的资源共享
  • 动态库升级简单, 只需要替换库文件, 无需重新编译应用程序
  • 程序猿可以控制何时加载动态库, 不调用库函数动态库不会被加载

缺点:

  • 加载速度比静态库慢, 以现在计算机的性能可以忽略
  • 发 布程序需要提供依赖的动态库

相关文章:

  • 思考如何学习一门编程语言?
  • 实习总结 --- 内部平台使用
  • 【聊聊原子性,中断,以及nodejs中的具体示例】
  • 通过MATLAB控制TI毫米波雷达的工作状态
  • react18.x+播放文本内容
  • Appium adb 获取appActivity
  • Laravel swagger接口文档生成和管理
  • 数据结构与算法—空间复杂度详解与示例(C#,C++)
  • 【力扣 - 每日一题】3115. 质数的最大距离(一次遍历、头尾遍历、空间换时间、埃式筛、欧拉筛、打表)Golang实现
  • Halcon机器视觉定位--模板匹配
  • Android启动时间分析
  • 7.2总结
  • 计算机相关术语科普之什么叫网关(Gateway)
  • llama3模型部署时遇到的问题及解决方案
  • 【ONLYOFFICE】| 桌面编辑器从0-1使用初体验
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • HTML5新特性总结
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • SpiderData 2019年2月13日 DApp数据排行榜
  • Spring Boot快速入门(一):Hello Spring Boot
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 搞机器学习要哪些技能
  • 全栈开发——Linux
  • 如何合理的规划jvm性能调优
  • 如何学习JavaEE,项目又该如何做?
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 一文看透浏览器架构
  • 源码安装memcached和php memcache扩展
  • 组复制官方翻译九、Group Replication Technical Details
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (1)STL算法之遍历容器
  • (4)(4.6) Triducer
  • (4)logging(日志模块)
  • (HAL库版)freeRTOS移植STMF103
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (SpringBoot)第二章:Spring创建和使用
  • (ZT)出版业改革:该死的死,该生的生
  • (层次遍历)104. 二叉树的最大深度
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (五)Python 垃圾回收机制
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (转)Linq学习笔记
  • (转)重识new
  • *Django中的Ajax 纯js的书写样式1
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .htaccess配置重写url引擎
  • .py文件应该怎样打开?
  • @EnableWebMvc介绍和使用详细demo
  • []error LNK2001: unresolved external symbol _m
  • [Angular 基础] - 数据绑定(databinding)
  • [BZOJ1089][SCOI2003]严格n元树(递推+高精度)