【Linux】详析动、静态库原理、创建与使用

image-20230401094505214


在Linux环境下, 我们使用gcc编译链接代码文件时, 可以统分为静态链接和动态链接

静态链接:在编译链接时, 将代码所使用到的静态库文件的代码全部加入到可执行文件中, 此时的可执行文件会生成的比较大. 不过, 此时可执行文件再运行时, 就不需要再查找库了. 静态库文件一般以 .a 结尾

动态链接:不会在编译链接时将动态库文件的代码加入到可执行文件中, 而是在可执行文件运行时, 去查找所需动态库, 并将其加载到相应的进程中. 并且不同进程可以共享这些动态库. 动态库文件一般以 .so 结尾

动静态库

在介绍理解动静态库之前, 一定要先知道什么是库?

其实简单一点理解, 别人所写的一些函数、方法等相关的代码文件经过归档、整合然后发布出来的供其他人使用 就是库

也就是说, 每个人都可以生成库吗?是的. 这也是本篇文章的主要内容之一

创建静态库和动态库

我们在使用gcc来编译代码的时候, 不加选项直接生成可执行程序:

额外使用两个.c文件:

// myMath.h:
#pragma once

#include <stdio.h>
#include <assert.h>

// 从from累加到to
extern int addToVal(int from, int to);
// myMath.c:
#include "myMath.h"

int addToVal(int from, int to) {
 assert(from <= to);

 int result = 0;
 for(int i = from; i <= to; i++) {
     result += i;
 }

 return result;
}
// myPrint.h:
#pragma once

#include <stdio.h>

extern void myPrint(const char* msg);
// myPrint.c
#include "myPrint.h"

void myPrint(const char* msg){
 printf("%s:: %d\n", msg, __TIME__);
}
image-20230322091636437

介绍静态链接

除了上面可以直接编译链接一步生成可执行文件之外, 还可以通过添加选项将编译链接的过程分离开, 可以使用 -c 选项将代码文件先编译成目标文件而先不做链接:

image-20230322085615053

生成.o 的下一步就是链接了, 那么而我们知道, 我们所谓的链接, 其实就是把这些生成的.o目标文件连接起来生成可执行程序

我们已经生成了.o, 如果此时我把这些 .o 文件给别人, 别人可以直接使用这些.o文件链接生成可执行文件吗?

答案是肯定的.

我们创建另一个目录, 并只将两个.o文件移动过去 而不移动.c文件, 并再main函数中使用两个函数:

image-20230322093852419

可以发现, 即使没有指定编译.c文件, 只将.o文件链接起来, 就可以成功生成一个可执行文件

这个过程, 其实就是静态链接的过程..o文件, 并不是静态库, 而**静态库, 则是由这些.o文件生成的**

创建静态库

上面我们演示了什么是静态链接, 也知道了 静态库其实就是由 源文件编译生成的.o目标文件 生成的, 那么该如何具体操作呢?

库的生成其实就是一个指令, 可以使用指令将需要做成库的.o文件 归档生成一个库

创建静态库的命令是:ar -rc 库名 .o文件 .o文件 ……

ar可以看作是归档的意思:archive, -rc则是 replace 和 creat 覆盖或创建

其中, 库名有严格的命名规则, 对于静态库, 必须以lib开头, 以.a为结尾后缀, 即 libxxxx.a

xxxx的部分, 一般以库内容决定

那么对于我们实现的两个简单的累加和print时间的函数来说, 我们就可以这样创建静态库:

image-20230322111304476

并且可以通过 ar -tv 查看静态库的信息:

image-20230322114733492
打包静态库

生成了静态库之后, 还并不能只将静态库打包发布出去. 因为静态库内提供的一般只是方法实现的代码, 而没有方法的声明, 也就是还需要头文件

也就是说, 发布静态库其实需要将静态库和头文件整合到一个目录中进行打包, 一般的操作是:

mkdir -p lib-static/include
mkdir -p lib-static/lib
cp *.a lib-static/lib
cp *.h lib-static/include
image-20230322213910529

创建动态库

动静态库都是由源码编译出的.o 目标文件生成的

但是, 生成动态库和静态库的.o目标文件是不同的!

gcc -c 生成的目标文件, 只能用于静态库的创建, 不能用于动态库的创建

创建动态库所用的.o文件, 是通过 gcc -fPIC -c 编译生成的

image-20230322214155688

这样生成的目标文件也是可以直接链接生成可执行文件的.

创建动态库不仅是.o目标文件的生成方式不同, 将目标文件生成为动态库文件的命令也不同

生成静态库使用的是系统提供的ar归档工具, 而生成动态库则需要使用gcc

gcc -shared -o, shared表示生成共享库格式

而动态库的命名也有严格的格式:libxxxx.so, 以lib为开头, 以.so后缀结尾

image-20230322220024492
什么是fPIC选项

PIC(Position Independent Code), 位置无关代码

我们知道, 目标文件链接为可执行程序的过程, 其实是将目标文件的代码加载到程序中, 并对代码分配地址的过程.

使用 gcc -c不加-fPIC 编译生成的目标文件是与位置有关的代码. 在链接时, 会将代码加载到程序中并且分配在进程中的绝对地址. 即 在调用静态库函数时, 会调用这个本来就表示函数的绝对地址. 既然是绝对地址, 那么程序加载到内存中时, 这部分代码是不能加载到随意的位置的, 只能加载到进程的固定位置, 供操作系统实际调用

而我们使用 gcc -fPIC -c 编译生成的目标文件是与位置无关的代码. 在链接时, 会将代码以一个相对地址的形式加载到程序中, 即 在调用动态库函数时, 会通过调用程序中的的一个表示函数位置的相对地址, 这个地址并不直接表示函数, 而是通过这个地址可以找到内存中加载的代码. 既然是相对的地址, 也就意味着其实动态库代码可以加载到内存的任意位置.

打包动态库

动态库的打包与静态库的打包可以用一样的方式

mkdir -p lib-dynamic/include
mkdir -p lib-dynamic/lib
cp *.so lib-dynamic/lib
cp *.h lib-dynamic/include
image-20230322220234075

动、静态库的使用

上面介绍了创建动、静态库的方法过程

那么创建了动态库或静态库之后该如何使用这些库呢?

静态库的使用

当前所在路径:

$ pwd
/home/July/gitCode/github/problem-of-learning/Linux/Code/CPP/libStaticDynamic/lib-static

当前在静态库打包的路径

在编写c语言时使用静态库:

image-20230322221356453

**在包含头文件时, 直接指定了头文件的相对位置 **

编译链接时出错. 报错的信息是:main函数中引用了未定义的addToVal和myPrint, 这是为什么?

我们包含了头文件, 但是在使用后在编译链接时却报错, 这是为什么?

我们在包含头文件的时候, 使用不同的符号 "" <> 编译器会在不同的路径下查询头文件.

""包含头文件, 编译器会在当前路径下查询头文件

<>包含头文件, 编译器会在系统的头文件目录下查询

Linux系统中, 系统头文件路径一般会在:/usr/include

用户安装的某些软件的头文件应该会在:/usr/local/include

使用库也是同理, 但是由于代码内不会去包含库文件路径, 所以 编译器默认会在系统的库文件路径下找需要的库文件

而我们的静态库文件在哪里?在我们指定的目录下, 但肯定没有在系统的库文件路径下.

那么怎么解决这个问题呢?

  1. 将我们的库文件添加到系统的库文件路径下

    Linux操作系统的库文件路径一般在:/lib64

    我们需要将静态库文件添加到此路径下:

    image-20230322223924103

    然后再编译链接:

    image-20230322223955105

    会发现, 还是错误.

    这是又是为什么呢?

    以往我们使用C语言时, 我们使用的都是c语言提供的库. 而gcc默认是认识c语言的库的, 但是它并不认识其他的第三方库, 比如我们的库. 它不认识我们的库, 那么即使我们的库就在系统库目录下、就在他眼前, 它也认不出来

    image-20230322224841491

    系统的库文件目录下, 已经存在了我们的库文件, 但是gcc不认识

    所以, 除了让gcc可以找到库文件, 还要让gcc认识库文件

    这是, 就需要使用 -l 选项, 来指定我们需要的库:

    image-20230322224353720

    gcc使用-l, 可以告诉gcc需要使用哪个库. 就是让gcc认识我们使用的库

    -l后需要跟库名, 但是并不需要跟完整的库名, 比如libxxxx.a, 只需要跟xxxx的部分

    -l后可以跟空格, 也可以不跟

    这样的方式, 其实就是将第三方库安装到了系统中.

    但是, 并不推荐直接将第三方库安装到系统的库文件目录下

    这样的操作其实是, 污染了系统库. 所以我们最好将刚刚添加的库文件删除了.

    删除之后, 再执行gcc语句:

    image-20230322225401054
  2. 指定头文件路径和库文件路径

    我们修改一下test.c的内容:

    #include "myPrint.h"
    #include "myMath.h"
    #include <stdio.h>
    
    int main() {
    	int ret = addToVal(20, 30);
    	printf("addToVal(20, 30): %d\n", ret);
    
    	myPrint("Hello world, hello July");
    
    	return 0;
    }
    

    再直接进行编译链接:

    image-20230322230823048

    头文件也找不到了, 而且库文件也肯定找不到, 使用的第三方库函数肯定也无法找到

    那么在不污染系统查找路径的前提下, 如何正确的编译链接呢?

    gcc 有两个选项:

    1. -I: 可以用来指定包含的头文件的路径
    2. -L: 可以用来指定所使用库文件的路径

    那么使用这两个选项:

    gcc test.c -I ./include -L ./lib -lMyfunc

    image-20230322231507473

动态库的使用

按照上面使用静态库的经验, 我们可以直接使用gcc -I -L -l 来对使用动态库的代码, 进行编译链接:

image-20230322233946223

执行命令不会报错, 但是当我们运行生成的可执行程序的时候:

image-20230322234149963

我们会发现, 进程无法找到相对应的动态库.

发生这种找不到库的错误的时候, 可以使用ldd命令来查看程序依赖的库:

image-20230322234749548

系统表示, 程序依赖的一个 libMyfun.so 库找不到.

这是为什么?我们在编译的时候 明明已经指定了库的路径以及库名, 为什么还是找不到?

只用一句话回答这个问题, 给编译器说了库路径和库名, 跟生成的可执行程序有什么关系?又没有给可执行程序说!

也就是说,

gcc编译的时候找到并使用了动态库 生成了可执行文件, 但是生成的可执行文件并不知道动态库在哪

那么如何解决这个问题呢?

  1. 在系统库目录下添加使用的动态库

    在/lib64目录下添加动态库, 就是将动态库安装到系统中

    系统的库目录, 不仅仅只是给gcc提供库的查找路径的, 而是给系统中的所有进程, 所以 在系统库目录下添加动态库, 所有进程就可以找到

    这个就不演示了

  2. 添加相应的环境变量

    Linux操作系统中, 有一个环境变量是用来 指定 进程动态运行时 查询库文件的路径的: LD_LIBRARY_PATH

    所以, 我们只要添加环境变量, 进程就会向环境变量下的目录中查找动态库

    举个例子:

    # 永久添加环境变量
    # 首先 先增加用户的写权限
    sudo chmod a+w /etc/profile
    vim /etc/profile
    # 在打开文件的最后一行输入
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/July/gitCode/github/problem-of-learning/Linux/Code/CPP/libStaticDynamic/lib-dynamic/lib
    # 保存退出之后, 关闭用户的写权限 再执行命令使修改生效
    sudo chmod a-w /etc/profile
    # 必须关闭用户写权限
    source /etc/profile
    

    然后再执行可执行程序:

    image-20230323002052643

    可以看到, 程序已经可以正常执行

    测试完之后, 可以取消设置

  3. 添加系统配置文件

    除了上面的添加环境变量来让进程可以找到动态库之外, 还有另外一种方法:添加系统配置文件

    Linux操作系统中 /etc/ld.so.conf.d 路径下, 保存的是搜索动态库的配置文件:

    image-20230323002503616

    我们可以添加一个类似的文件, 来让进程知道向哪里查询动态库

    但是文件内容是什么呢?

    可以先查看一下已经存在的文件:

    image-20230323002639608

    可以看到, 其实这些配置文件的内容, 就是一个动态库所在的路径

    那么我们也可以比葫芦画瓢, 添加一个指定动态库路径的配置文件, 文件名可以随便写:

    image-20230323003334543

    添加了配置文件之后, 可执行程序还是不能正常运行的.

    因为我们添加的配置文件还没有被加载到系统内存中, 所需需要使用命令:ldconfig 配置文件

    image-20230323003609394

    可以删除配置文件后, 执行ldconfig 来取消配置文件的加载

  4. 添加软连接

    这个方法与第一种方法类似, 也是想系统的库文件目录下添加文件

    不过此时添加的是动态库的软连接, 而不是原本的动态库文件:

    image-20230323005804169

    添加之后, 可以正常执行程序

    并且, 此时在编译的时候, 也不需要指定库的目录了!

    image-20230323010241075

为什么使用静态库的可执行程序没有查找不到库的情况?

为什么使用静态库链接成的可执行文件, 在执行的时候就没有找不到静态库呢?

是因为, 使用静态库链接时, 静态库的相关代码已经被加入到了可执行文件中, 即 使用静态库生成的可执行文件, 在执行时并不依赖任何静态库

也就不需要运行时查找

为什么使用第三方动态库的可执行程序需要知道动态库的路径?

首先我们的可执行程序运行之后, 会被加载到内存中变成一个进程

操作系统会为其创建:PCB、进程地址空间、页表

image-20230323011553678

我们知道, 动态库在链接时是不会将代码加载到程序中的, 只会将代码以一个相对地址的形式加载到程序中

而我们的程序在运行到动态库代码时, 是需要跳转到动态库代码继续执行的

而动态库是一个可执行文件, 它拥有x执行权限

image-20230323011931508

如果程序不知道其所使用的动态库的路径, 那么进程在运行时, 操作系统就不能根据程序把动态库加载到内存中

如果动态库不能被加载到内存中, 进程的虚拟地址空间的共享区就不能指向动态库的代码

如果共享区不能指向动态库的代码, 那么进程肯定就不能执行代码, 进程就会执行错误

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Java中,@RequestBody和@RequestParam是用于处理HTTP请求参数的注解。它们之间有一些区别,下面详细解析一下: 1. @RequestBody注解用于获取请求体中的参数,并将其绑定到方法的参数上。通常用于处理POST请求中的表单数据或者JSON数据。当使用@RequestBody注解时,Spring框架会自动将请求体中的数据转换为方法参数所需的对象。例如: ```java @PostMapping("/user") public void addUser(@RequestBody User user) { // 处理user对象 } ``` 在上面的例子中,@RequestBody注解将请求体中的数据转换为User对象,并将其绑定到addUser方法的参数上。 2. @RequestParam注解用于获取URL中的请求参数,并将其绑定到方法的参数上。通常用于处理GET请求或者POST请求中的URL参数。当使用@RequestParam注解时,Spring框架会自动从URL中提取指定名称的参数,并将其转换为方法参数所需的类型。例如: ```java @GetMapping("/user") public void getUser(@RequestParam("id") int userId) { // 处理userId参数 } ``` 在上面的例子中,@RequestParam注解将URL中名为"id"的参数提取出来,并将其转换为int类型,并将其绑定到getUser方法的参数userId上。 总结起来,@RequestBody注解用于获取请求体中的参数,适用于处理POST请求中的表单数据或者JSON数据。而@RequestParam注解用于获取URL中的请求参数,适用于处理GET请求或者POST请求中的URL参数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

七月.cc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值