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

【Linux】动静态库

目录

  • 1. 静态库
    • 1.1 生成静态库
    • 1.2 静态链接
      • 1.2.1 带选项编译链接
      • 1.2.2 安装库到系统库路径
      • 1.2.3 系统库路径下建立软链接
  • 2. 动态库
    • 2.1 生成动态库
    • 2.2 动态链接
    • 2.3 动态库的加载方式
      • 2.3.1 拷贝到系统默认的库路径
      • 2.3.2 建立软链接
      • 2.3.3 环境变量法
      • 2.3.3 建立动态库路径配置文件
    • 2.4 动静态库的区别

在 linux 系统中,形如 libXXX.a 的文件称为静态库,对应是的静态链接;形如 libYYY.so 为动态库,对应的是动态链接,也是 gcc/g++ 的默认编译行为。但是对于动静态库 和 动静态链接,远远不只这些。

1. 静态库

1.1 生成静态库

// mymath.h 文件
#pragma once                                                                                                                       
#include<stdio.h>                                                                                                             
extern int myerrno;                                                                                                                                                   
int add(int x, int y);                                                   
int sub(int x, int y);                                   
int mul(int x, int y);                                  
int div(int x, int y);         
// mymath.c 文件
#include "mymath.h"     
int myerrno = 0;        
int add(int x, int y) { return x + y; }        
int sub(int x, int y) { return x - y; }       
int mul(int x, int y) { return x * y; }    
int div(int x, int y) {    if(y == 0){    myerrno = 1;    return -1;                                                                                             }    return x / y;    
}    

将我们自己编写的方法提供给其它文件,有两种途径:

  1. 提供源代码,在其它文件下包含该头文件,编译时,将方法实现一同编译
  2. 把源代码打包成库,最终提供库 + 头文件‘
lib=libmymath.a    	# 静态库的名称# ar --- 生成静态库(把所有 .o 文件打包形成 .a 文件)
# -rc --- replace 和 create 的意思,如果目标文件存在则替换,不存在则创建。
$(lib):mymath.o    ar -rc $@ $^    # 我们 gcc 多文件编译时,就是把每个文件编译为 .o 文件,最后链接起来形成可执行程序
# 而所谓静态链接,无非就是先将源代码都编译为 .o 文件,把所有 .o 文件打包,命令为形如 libXXX.a 的文件
# 将来只需要将我们的 main.c 之类的源文件编译为 .o 文件,然后与 libXXX.a 结合,这就是静态链接!
mymath.o:mymath.c    gcc -c $^    .PHONY:clean    
clean:    rm -rf *.o *.a lib                                                                                         # 发布库
.PHONY:output    
output:    mkdir -p lib/include    mkdir -p lib/mymathlib    cp *.h lib/include    cp *.a lib/mymathlib 

在这里插入图片描述

这是因为找不到头文件,编译时,编译器会在系统头文件 和 当前路径下查找头文件,但这两个地方都没有 mymath.h。

1.2 静态链接

1.2.1 带选项编译链接

gcc main.c -I ./lib/include/ -L	./lib/mymathlib/ -lmymath
// -I 指定头文件所在目录
// -L 指定方法的实现所在目录
// -l 指定方法的实现所在目录中具体的库文件(一般不带空格)
// 库的文件名为 libmymath.h,但库的真实名字为 mymath(即去除lib前缀和文件格式后缀) 

在这里插入图片描述

在这里插入图片描述

关于 gcc/g++ 的默认编译行为都是动态链接的,但是当我们没有动态库,只有静态库的时候,即便编译选项不带 -static ,也只能是静态链接(因为只能静态链接啊,没有动态库啊,至少在编译时,需要用到的库,是静态链接的)。也可以理解为 -static 选项只是一个给编译器建议性的选项,在有相应的静态库时,编译器会听取建议。

1.2.2 安装库到系统库路径

静态链接也可以不用在编译时带上 -I-L 选项,如果直接把库文件拷贝到系统路径下,那么也可以实现编译时的静态链接,但是依旧需要带上 -l 选项指明具体要链接的库文件。(该做法一般不推荐)

在这里插入图片描述

1.2.3 系统库路径下建立软链接

#include "myinc/mymath.h"  	 // 建立了软链接后,头文件指向软链接即可
int main()    
{    int ret = div(10, 0);    printf("10 / 0 = %d, errno = %d\n", ret, myerrno);    return 0;    
}  

在这里插入图片描述
在这里插入图片描述

但是无论是哪种链接方式,都需要在编译时指明特定的库文件,要不然编译器无法得知使用哪些静态库进行链接。

软链接的其中一个应用场景就是如此,可以在系统路径下通过建立软链接,快速定位各种库文件,而不再需要将整个库拷贝到系统路径下。


2. 动态库

2.1 生成动态库

// myprint.h
#pragma once
#include<stdio.h>
void Print();
// myprint.c
#include "myprint.h"
void Print()
{printf("This is a dynamic-linking test!\n");printf("This is a dynamic-linking test!\n");printf("This is a dynamic-linking test!\n");
}
// mylog.h
#pragma once
#include<stdio.h>
void Log(const char* msg);
// myprint.c
#include "mylog.h"
void Log(const char* msg) { printf("Warning: %s\n", msg); }

生成动态库的核心思想与静态库是一致的,都是把各源文件先编译成 .o 文件,然后把所有 .o 文件打包,最后用户把自己的源文件编译为 .o 文件,再与库文件进行链接就行了。因此在链接之前,无论动静态库,在链接之前都需要先编译为 .o 文件。

静态库是通过命令 rc 打包生成的,但动态库并不直接通过系统指令来打包,动态库的生成一系列工作是内嵌在 gcc/g++ 编译器当中,可以理解动态库/动态链接与编译器的关系是最亲近的,这也就能够进一步理解为什么编译器的默认链接都是动态链接了。

  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so
gcc -fPIC -c mylog.c	# 先带上 -fPIC 生成 .o 文件
gcc -fPIC -c myprint.c
gcc -shared -o libmymethod.so *.o	# 正常 -o 选项编译为生成可执行程序,加上 -shared 指明不直接生成可执行,而是生成一个共享库

在这里插入图片描述

静态库提供源代码的(二进制文件数据),当用户的可执行程序使用到静态库的数据时,把静态库的源代码拷贝到用户的可执行程序,拷贝完成后,用户的源文件编译就全部完成,跟静态库就再也没关系了,而将来可执行程序运行起来变为进程,静态库是不需要被加载到内存的,因为需要的静态库的数据,已经拷贝到可执行程序里面了。

而动态库它并不像静态库一样,直接拷贝的形式,当可执行程序执行时,需要访问动态库的数据,就需要跳转到动态库去执行,动态库要被执行,就注定了要被加载到内存中。有了可执行权限作为前提,库文件才能快速的被系统加载到内存中(不排除没有 x 权限的文件也会被系统加载)。总而言之,给动态库带上 x 权限,也是一种 “动态库需要被执行” 的含义,虽然动态库里面没有 main 函数,但是它提供的方式是将来用户的可执行程序可能要访问的,也算是用户可执行程序的一部分了,因此也算是用户的可执行程序的一部分(动态库不是不能被执行,只是不能被单独执行!!!)。


# Makefile
dy-lib=libmymethod.so
static-lib=libmymath.a# 同时生成多个目标文件
.PHONY:all
all: $(dy-lib) $(static-lib)$(static-lib):mymath.oar -rc $@ $^
mymath.o:mymath.cgcc -c $^$(dy-lib):myprint.o mylog.ogcc -shared -o $@ $^	# -shared 生成共享库
myprint.o:myprint.cgcc -fPIC -c $^			# -fPIC 产生位置无关码
mylog.o:mylog.cgcc -fPIC -c $^.PHONY:clean
clean:rm -rf *.o *.a mylib.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.a mylib/libcp *.so mylib/lib

在这里插入图片描述

2.2 动态链接

// main.c
#include "mylog.h"
#include "myprint.h"
int main()
{Print();Log("This is a log message!\n");return 0;
}

与静态链接一样,编译时直接带上 -I 确定头文件路径,-L 确定库文件路径,-l 确定库文件。

gcc main.c -I mylib/include/ -L mylib/lib/ -lmymethod

在这里插入图片描述

编译成功了,也即动态链接这个过程并没有问题,但是当程序运行起来之后,却显示 “file not found” 这样的字眼,我们不说已经通过各种选项,告诉编译器头文件、库文件在哪了吗??为什么还会找不到呢??

其实,编译器它确实找到了,但是动态库它不像静态库啊,编译链接完就没它啥事了。当程序运行起来,变为进程,该进程的代码推进时,如果调用了动态库中的数据(即方法),那么操作系统(加载器)就需要去执行动态库的方法,但是加载器不知道动态库在哪啊!

想要搞清楚动态库加载的问题,就需要先清楚那为什么诸如 libc.so.6 这样的系统中的动态库可以被正常加载。系统指令在执行时不需要携带路径,而我们自己的 exe 程序需要路径执行,这是因为环境变量的存在。而编译器有自己默认的搜索头文件、库文件的路径,操作系统同样也有一套自己默认的搜索动态库路径,这些路径都是操作系统内置好的,所以要想让动态库顺利被加载,就需要解决系统的搜索问题!

2.3 动态库的加载方式

关于动态库加载的办法,有以下四种。

2.3.1 拷贝到系统默认的库路径

将动态库拷贝到系统路径的 /lib64 或者 /usr/lib64 即可解决动态库的加载问题(与静态库相似,因此不多言)

2.3.2 建立软链接

sudo ln -s /home/outlier/linux/mylib/test/mylib/lib/libmymethod.so /lib64/libmymethod.so

在这里插入图片描述

在动态链接之后,由于加载器找不到动态库的问题,在系统默认库路径下建立一下动态库的软链接,甚至都不需要重新编译链接,该动态库就能够被系统找到了。

2.3.3 环境变量法

  • LD_LIBRARY_PATH:搜索用户自定义的库路径

将自己的库所在路径添加到系统环境变量的 LD_LIBRARY_PATH 中,同样可以解决动态库加载的问题!

echo $LD_LIBRARY_PATH 
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx		# 追加环境变量的搜索路径(会话周期性)

在这里插入图片描述

2.3.3 建立动态库路径配置文件

cd /etc/ld.so.conf.d
touch mylib.conf
echo "/home/xxx/xxx/xxx/xxx/mylib/lib" > mylib.conf		# 写入路径
ldconfig 	# 加载配置文件

系统维护动态库的配置文件,文件内存储的是动态库的路径,只要在 /etc/ld.so.conf.d 这个目录下建立一个 .conf 的配置文件,把动态库的路径写入配置文件,那么系统就能够找到动态库的路径。

在这里插入图片描述

动态库路径的配置文件不关注文件名,但需要注意的是,如果存在多个动态库 且 位于同一路径下,可以不需要建立多个路径配置文件,但如果不在同一个路径下,则需要建立多个配置文件来导入多个路径(一个配置文件一个路径)。

实际开发中,企业用的各种第三方库,都是很成熟的库了,因此都采用直接按照到系统库路径下的方式加载动态库,这种方法只是我们平时学习时不使用(因为我们的库不成熟,很简陋,按照到库路径下,有点污染库的意思哈哈哈)。

2.4 动静态库的区别

#include "mymath.h"
#include "mylog.h"
#include "myprint.h"
int main()
{int ret = add(1, 1);printf("1 + 1 = %d, errno = %d\n", ret, myerrno);Print();Log("This is a log message!\n");return 0;
}

在这里插入图片描述
在这里插入图片描述

程序编译完后,静态库被删除了,程序不会受到任何影响,因为需要用到的静态库的内容,已经在编译链接时拷贝到自己的可执行程序中了;但是动态库被删除了,程序就运行不起来了,这就是动静态库最本质的区别,动态库并不做拷贝行为,而是在程序运行起来之后,在调用动态库的地方,转而运行动态库的内容,如今动态库没了,程序也就自然运行不起来了。

并且我们可以看到的是,动态库只有一个,但是当多个可执行程序依旧可以正常运行,这也就说明了,动态库是被多个程序所共享的!因此动态库也称为共享库。即便有10个可执行程序,只要调用的是这个动态库的内容,那么在操作系统中,只需要加载这一个动态库,也只加载一次即可,并不需要因为有多个可执行程序而加载多次动态库。

系统指令也可以很好的印证上述论点,诸如 ls、pwd 等指令都用到了系统库 lic.so.6,但系统中就只有这一个 lic.so.6 动态库,并没有存在多份,但各指令依旧正常运行,因为它们都是共享同一个动态库的。

所以,动态库在系统中加载之后,会被所有进程共享! 所有进程共享一个库,系统中重复的代码就减少了,同时也可以大大节省内存。

在这里插入图片描述


如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!


http://www.mrgr.cn/news/30245.html

相关文章:

  • 【LeetCode】【算法】64. 最小路径和
  • TP6将HTML转换为PDF文件,非法UTF-8编码和中文乱码问题
  • leetcode86:分隔链表
  • 数据库基础(14) . MySQL存储过程
  • react-router-dom 库作用
  • 类在jvm之间的流转和存储哪些值
  • python怎么打开文件对话框
  • web自动化学习笔记
  • [leetcode] 69. x 的平方根
  • Hive自定义函数——简单使用
  • 为大模型提供服务需要多少 GPU 显存?
  • Linux中使用Docker容器构建Tomcat容器完整教程
  • 【深度学习】(3)--损失函数
  • Go 并发模式:扩展与聚合的高效并行
  • 二、各种热型
  • openmv与stm32通信
  • 前端大屏自适应方案
  • 基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
  • 阿里云kafka消息写入topic失败
  • jenkins 部署到tomcat
  • 2024最新的软件测试面试八股文(答案+文档)
  • Xmind软件自定义安装,如何安装在指定位置(不修改注册表),修改默认安装到c盘软件的安装位置
  • rpm 与 yum
  • JAVA客户端发送图片给服务端案例
  • JavaWeb笔记整理——Redis
  • 信息收集常用指令