Linux快速入门之 静态库和动态库 (07)

本文详细介绍了Linux环境下静态库和动态库的生成、使用及常见问题。静态库在编译时与可执行文件链接,不占用运行时内存,但可能导致空间浪费。动态库在运行时加载,节省空间,便于更新。制作静态库使用ar工具,动态库则需添加-fPIC和-shared参数。动态库加载问题可通过调整LD_LIBRARY_PATH、更新/etc/ld.so.cache或拷贝到系统库目录解决。
摘要由CSDN通过智能技术生成

7.静态库和动态库

程序中调用的库分为: 静态库 、动态库 ,在Linux和Windows中虽然库文件的格式和后缀有所不同,但是其工作原理是一样的,(编译好的二进制格式的源文件)

使用库文件时还需要这些库中提供的API函数的声明(头文件),添加到项目中便可以使用。

使用库文件的目的:

  • 为了使项目更加简洁,不同添加过多源码(.c /.cpp)
  • 为了保证源码保密,不希望其他人获得源码(库文件为二进制文件,人看不懂)

使用库文件例子:

A公司做一个项目 已经有500个cpp了,部分功能委托给B公司,B公司完成该功能写了300个cpp文件,B公司会打包成库文件交付给A公司 ;

对于A公司来说,避免了多出现300个cpp 导致代码繁多 ,对于B公司来说,自己的源码不会泄露,如果需要迭代功能自己可以反复收取服务费;

静态库和动态库是什么 ?有什么区别?

在这里插入图片描述

静态库: 为啥叫静态库,是在链接阶段,将汇编生成目标文件(.o格式)与引用到的库一起链接打包到可执行文件中,这种链接方式称为静态链接。

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

静态库在内存中存在多份拷贝导致空间浪费,例如一个静态库1m,有1000个同样程序,会占用1G内存,静态库对程序的更新、部署也会相对麻烦,一旦更新,使用它的所有程序都需要重新编译,发布给用户

动态库: 动态库在编译时不会链接到目标代码中 , 在程序运行时才被载入,不同的程序如果调用相同的库,内存中只存在一份共享库,解决了空间浪费的问题,同时因为在运行时加载库文件,也解决了静态库更新麻烦的问题,用户只需要更新动态库,增量更新

在这里插入图片描述

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期。
  • 可以实现进程之间的资源共享。(因此动态库也称为共享库)
  • 将一些程序升级变得简单。
  • 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
7.1 Linux下静态库

Linux系统下静态库由 ar程序完成 (现在大部分程序使用动态库)

Linux中静态库以 lib 为前缀以 .a 为后缀 ,中间为自己取的名字(例如 :libDemo.a

Windows中静态库通常 lib 为前缀以 lib 为后缀 中间为自己取的名字(例如 :libDemo.lib

7.1.1生成静态库

制作静态链接库需要分两部 :

  1. 将目标文件(各种源文件)进行汇编操作(-c) 生成二进制代码
  2. 通过 ar工具 将目标文件打包成静态库文件

ar 工具参数 :使用的时候 直接 rcs

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

#将目标文件进行汇编处理
$ gcc  源文件(*.c)  -c

#将获得的二进制文件(.o) 使用ar工具打包
$ ar rcs  静态库的名字(libDemo.a)  原材料 (*.o)

#发布静态库
1.提供头文件 **.h
2.提供制作好的静态库

举例

liu@liu-Ubuntu:~/StutyLinux$ tree
.
├── addNum.cpp    #包含一个加法函数
├── diviNum.cpp    #包含一个初法函数
├── include   #将头文件放置在子文件夹中
│   └── head.h                                                 #包含各个函数的函数声明
├── multNum.cpp    #包含一个乘法函数
└── subNum.cpp     #包含一个减法函数
 
1 directory, 5 files
liu@liu-Ubuntu:~/StutyLinux$ gcc *.cpp -c -I ./include/  #目标文件 汇编处理 并指定头文件路径
liu@liu-Ubuntu:~/StutyLinux$ ar rcs libMyNum.a *.o    #使用ar 工具打包为静态库
liu@liu-Ubuntu:~/StutyLinux$ ls
addNum.cpp  diviNum.cpp  include     multNum.cpp  subNum.cpp
addNum.o    diviNum.o    libMyNum.a  multNum.o    subNum.o

#将生成的静态库 libMyNum.a 和库文件对应的头文件 head.h  一并发布给使用者
head.h     #函数声明
libMyNum  #函数定义(二进制)
7.1.2静态库使用

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

# 4. 编译的时候指定库信息
	-L: 指定库所在的目录(相对或者绝对路径)
	-l: 指定库的名字, 掐头(lib)去尾(.a) ==> MyNum
# -L -l, 参数和参数值之间可以有空格, 也可以没有  -L./ -lcalc


liu@liu-Ubuntu:~/StutyLinux/test$ ls
head.h  libMyNum.a  main.cpp
liu@liu-Ubuntu:~/StutyLinux/test$ g++ main.cpp -o APP -L ./ -l MyNum
liu@liu-Ubuntu:~/StutyLinux/test$ ./APP 
a + b = 30
a - b =  10
a * b = 200
a / b = 2

使用静态库时,注意 -L 添加库的路径 -l 添加库的名称(掐头去尾)

静态库中不会出现动态库中可能无法被加载的问题

7.2 Linux下动态库

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

Linux中静态库以 lib 为前缀以 .so为后缀 ,中间为自己取的名字(例如 :libDemo.so

Windows中静态库通常 lib 为前缀以 dll 为后缀 中间为自己取的名字(例如 :libDemo.dll

7.2.1 生成动态库

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

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

在这里插入图片描述

#源文件进行汇编操作 ,使用参数 -c  
#还需要添加额外参数  -fpic
$ gcc 源文件(*.c)  -c  -fpic 

#将若干个 .o文件打包成动态库
$ gcc -shared 目标文件(*.o)  -o  动态库 (libMyNum.so)

#发布动态库和头文件
1.提供头文件 xxx.h
2.提供动态库 libxxx.so
7.2.2 制作动态库

以以上静态库案例中 (加减乘除文件为例),制作动态库

  1. 使用 gcc 将源文件进行汇编 (参数-c), 生成与位置无关的目标文件,需要使用参数 -fpic或者-fPIC
  2. 使用 gcc 将得到的目标文件打包生成动态库,需要使用参数 -shared
liu@liu-Ubuntu:~/StutyLinux$ tree      #显示当前文件夹结构
.
├── addNum.cpp
├── diviNum.cpp
├── include
│   └── head.h
├── multNum.cpp
├── subNum.cpp
└── test
    ├── APP
    ├── head.h
    ├── libMyNum.a
    └── main.cpp

2 directories, 9 files
liu@liu-Ubuntu:~/StutyLinux$ g++ *.cpp -c -fpic  -I ./include/   #汇编源文件  并指定头文件目录   -fpic
liu@liu-Ubuntu:~/StutyLinux$ ls
addNum.cpp  diviNum.cpp  include      multNum.o   subNum.o
addNum.o    diviNum.o    multNum.cpp  subNum.cpp  test
liu@liu-Ubuntu:~/StutyLinux$ g++ *.o -o libAP.so  -shared         #打包为动态库   -shared

#发布 库文件  和 相关头文件
1.head.h
2.libAP.so
7.2.3 动态库的使用

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

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

$ g++ main.cpp -o myDll -L  ./include  -l AP

liu@liu-Ubuntu:~/StutyLinux/testdll$ g++ main.cpp  -o myDll -L ./ -l AP
liu@liu-Ubuntu:~/StutyLinux/testdll$ ./
bash: ./: 是一个目录
liu@liu-Ubuntu:~/StutyLinux/testdll$ ./myDll 
a + b = 30
a -b = -10
a * b = 200
a /b = 0

检查动态库是否可以加载

#检查命令
$ ldd  可执行文件名

#如发现动态库无法加载  ,会显示 not  found
liu@liu-Ubuntu:~/StutyLinux/testdll/bb$ ldd myDll 
	linux-vdso.so.1 (0x00007fff207d9000)
	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1e0476c000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1e0437b000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1e03fdd000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f1e04cf7000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1e03dc5000)

7.2.4 动态库无法加载问题

在程序编译的最后一个阶段也就是链接阶段:

在 gcc 命令中虽然指定了库路径 (使用参数 -L ), 但是这个路径并没有记录到可执行程序中,只是检查了这个路径下的库文件是否存在。
同样对应的动态库文件也没有被打包到可执行程序中,只是在可执行程序中记录了库的名字。
可执行程序被执行起来之后:

程序执行的时候会先检测需要的动态库是否可以被加载,加载不到就会提示上边的错误信息
当动态库中的函数在程序中被调用了, 这个时候动态库才加载到内存,如果不被调用就不加载
动态库的检测和内存加载操作都是由动态连接器来完成的

动态连接器

动态链接器是一个独立于应用程序的进程,属于操作系统,当用户的程序需要加载动态库的时候动态连接器就开始工作了,很显然动态连接器根本就不知道用户通过 gcc 编译程序的时候通过参数 -L 指定的路径。

  1. 可执行文件内部的 DT_RPATH(不可改)

  2. 系统的环境变量 LD_LIBRARY_PATH

  3. 系统动态库的缓存文件 /etc/ld.so.cache

    通常不能修改,但是可以通过/etc/idso.config 同步进缓存文件)

  4. 存储动态库 / 静态库的系统目录 /lib/, /usr/lib 等

按照以上四个顺序,依次搜索,找到之后结束遍历,最终还是没找到,动态连接器就会提示动态库找不到的错误信息。

解决方案

可以提供三种解决方案,我们只需要将动态库的路径放到对应的环境变量或者系统配置文件中,同样也可以将动态库拷贝到系统库目录(或者是将动态库的软链接文件放到这些系统库目录中)。

方案 1: 将库路径添加到环境变量 LD_LIBRARY_PATH

找到相关的配置文件

用户级别: ~/.bashrc (家目录下隐藏文件)—> 设置对当前用户有效
系统级别: /etc/profile —> 设置对所有用户有效
使用 vim 打开配置文件,在文件最后添加这样一句话

# 自己把路径写进去就行了
export LIBRARY_PATH=$LIBRARY_PATH:动态库的绝对路径

让修改的配置文件生效

修改了用户级别的配置文件,关闭当前终端,打开一个新的终端配置就生效了

修改了系统级别的配置文件,注销或关闭系统,再开机配置就生效了

或者使用命令行更新

# 修改的是哪一个就执行对应的那个命令
# source 可以简写为一个 . , 作用是让文件内容被重新加载
$ source ~/.bashrc          (. ~/.bashrc)
$ source /etc/profile       (. /etc/profile)

方案 2: 更新 /etc/ld.so.cache 文件

找到动态库所在的绝对路径(不包括库的名字)比如:/home/robin/Library/

使用 vim 修改 /etc/ld.so.conf 这个文件,将上边的路径添加到文件中 (独自占一行)

# 1. 打开文件
$ sudo vim /etc/ld.so.conf

# 2. 添加动态库路径, 并保存退出

#更新 /etc/ld.so.conf 中的数据到 /etc/ld.so.cache 中
# 必须使用管理员权限执行这个命令
$ sudo ldconfig   

方案 3: 拷贝动态库文件到系统库目录 /lib/ 或者 /usr/lib 中 (或者将库的软链接文件放进去)

# 库拷贝
sudo cp /xxx/xxx/libxxx.so /usr/lib

# 创建软连接
sudo ln -s /xxx/xxx/libxxx.so /usr/lib/libxxx.so

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值