动态库和静态库

目录

一、动静态库的本质与编译流程

1.1 程序的诞生过程

1.2 库的定位

二、动静态库的直观对比

2.1 示例代码

2.2 查看可执行程序依赖的库

2.3 动静态库的区别

2.4 静态链接与动态链接

三、动静态库各自的特征

3.1 静态库

3.2 动态库

四、静态库的打包与使用

4.1 示例代码

头文件内容

源文件内容

4.2 打包静态库

1. 生成目标文件

2. 生成静态库

3. 查看静态库内容

4. 组织头文件和库文件

4.3 使用静态库

1. 创建主程序 main.c

2. 方法一:使用编译选项

3. 方法二:将头文件和库文件拷贝到系统路径

总结:

五、动态库的打包与使用

5.1 打包动态库

1. 生成位置无关的对象文件

2. 生成动态库

3. 组织头文件和库文件

5.2 使用动态库

1. 创建主程序 main.c(同前)

2. 使用编译选项

3. 解决动态库运行时依赖问题

总结


一、动静态库的本质与编译流程

1.1 程序的诞生过程

从源代码到可执行程序需经历四个关键阶段:

# 预处理(展开头文件/宏替换)
gcc -E test.c -o test.i

# 编译(生成汇编代码)
gcc -S test.i -o test.s

# 汇编(生成机器码)
gcc -c test.s -o test.o

# 链接(组合目标文件)
gcc test.o -o test

1.2 库的定位

        动静态库本质是目标文件的集合,是软件复用的重要手段。当多个项目需要相同功能模块时:

  • test1.c、test2.c 等公共代码编译为目标文件

  • 打包目标文件形成库文件

  • 新项目只需链接库文件即可复用功能

        例如,用test1.ctest2.ctest3.ctest4.c和 main1.c生成可执行文件时,需要先生成各个文件的目标文件(test1.otest2.o等),再将它们链接起来。如果这些文件在多个项目中频繁使用,可以将它们的目标文件打包成一个库,供其他项目直接链接使用。

二、动静态库的直观对比

在Linux下,我们可以通过以下代码和命令来认识动静态库:

2.1 示例代码

#include <stdio.h>

int main() 
{
    printf("hello world\n"); 
    return 0;
}

编译并运行该代码:

2.2 查看可执行程序依赖的库

使用 ldd 命令查看可执行程序依赖的库文件:

ldd test

输出结果中会包含libc.so.6,这是C标准库的动态库,我们通过ls命令还可以发现libc.so.6实际上只是一个软链接。

实际上该软链接的源文件libc-2.31.solibc.so.6在同一个目录下,为了进一步了解,我们可以通过file 文件名命令来查看libc-2.31.so的文件类型。 

可以看到,libc.so.6是一个动态库(共享库)。

2.3 动静态库的区别

  • 动态库:以 .so为后缀(Linux)或 .dll为后缀(Windows)。程序运行时动态链接库代码,多个程序共享同一份库代码。

  • 静态库:以 .a为后缀(Linux)或 .lib为后缀(Windows)。程序编译时将库代码复制到可执行文件中,运行时不再依赖库。

2.4 静态链接与动态链接

默认情况下,gcc编译器使用动态链接。若要进行静态链接,可以使用 -static 选项:

gcc -o test test.c -static

静态链接生成的可执行程序文件较大,但不依赖外部库。此时当我们使用ldd 文件名命令查看该可执行程序所依赖的库文件时就会看到以下信息:

三、动静态库各自的特征

特性静态库动态库
链接时机编译时运行时
文件包含完整代码拷贝符号表引用
内存占用独立副本共享内存映射
更新维护需重新编译热替换更新
典型文件后缀.a (Linux), .lib (Windows).so (Linux), .dll (Windows)

3.1 静态库

        由于静态库的代码是直接复制到可执行文件中的,所以生成的可执行文件会比较大,占用更多的磁盘空间。但这也意味着, 可执行文件在运行时不再需要外部的库支持,可以单独运行,完全不依赖其他文件,就好比是一个“全能型”的独立个体,自带所有需要的工具和资源。

  • 优点:生成的可执行程序独立运行,不依赖外部库。

  • 缺点:可执行程序体积较大,多个程序使用相同库时会导致内存中存在重复代码。

3.2 动态库

        动态库就像是一家饭店,饭店的菜单上写着各种菜名(相当于函数入口地址表),但只有等客人点菜了,厨师才会做这道菜(操作系统动态加载动态库代码)。这样,饭店不用把所有的菜都提前做好存在厨房里(节省磁盘空间),而且很多客人点同一道菜,厨师只需要做一份,然后给需要的客人盛到各自的盘子里(多个程序共享动态库)。这种方式既减少了厨房食材的存储空间(节省磁盘空间),也避免了同一道菜在多个盘子里重复摆放浪费桌子空间(节省内存,因为物理内存中只有一份动态库实例)。

  • 优点:节省磁盘空间和内存,多个程序共享同一份库代码。

  • 缺点:必须依赖动态库,否则程序无法运行。

四、静态库的打包与使用

4.1 示例代码

假设我们有以下文件:

  • add.hsub.h(头文件)

  • add.csub.c(源文件)

头文件内容

add.h

#pragma once

extern int add(int x, int y);

sub.h

#pragma once

extern int sub(int x, int y);
源文件内容

add.c

#include "add.h"

int add(int x, int y) 
{
    return x + y;
}

sub.c

#include "sub.h"

int sub(int x, int y) 
{
    return x - y;
}

4.2 打包静态库

1. 生成目标文件

        使用以下命令将源文件 add.csub.c 编译为目标文件:

gcc -c add.c sub.c

        执行此命令后,编译器会生成两个目标文件:add.osub.o。这些 .o 文件是编译后的二进制目标文件,包含了源文件中函数的汇编代码,但尚未最终链接为可执行文件。

2. 生成静态库

        使用以下命令将生成的目标文件打包为静态库:

ar -rc libcal.a add.o sub.o
  • -r: 表示插入目标文件,如果库中已存在同名的文件,会自动替换。

  • -c: 表示如果库文件不存在,则创建一个新库。

  • libcal.a: 是生成的静态库文件的名称。 打包完成后,libcal.a 静态库文件便创建成功。

3. 查看静态库内容

使用以下命令可以查看静态库文件的内容:

ar -tv libcal.a
  • -t: 表示列出库中的文件。

  • -v: 表示以详细模式显示文件信息。 执行命令后,会显示库中包含的目标文件的详细信息,如文件名、修改时间、所有者、权限、文件大小等。

4. 组织头文件和库文件

        将相关的头文件和库文件整理到指定的目录中,最终的文件夹结构如下:

  • include/ 目录用于存放头文件,如 add.hsub.h。这些头文件包含了函数的声明,供其他代码文件引用。

  • lib/ 目录用于存放生成的静态库文件 libcal.a。该库文件包含了编译后的目标代码,供链接器在链接阶段使用。

4.3 使用静态库

1. 创建主程序 main.c

        创建一个名为 main.c 的文件,该文件使用了我们之前生成的静态库中的函数。以下是 main.c 的内容:

#include <stdio.h>
#include "add.h" // 包含自定义函数的头文件

int main() {
    int x = 20, y = 10, z = add(x, y); // 调用 `add` 函数计算两数之和
    printf("%d + %d = %d\n", x, y, z); // 输出计算结果
    return 0;
}

2. 方法一:使用编译选项

        使用以下命令将 main.c 编译成可执行文件 main

gcc main.c -I./mathlib/include -L./mathlib/lib -lcal -o main

解释:

  • -I./mathlib/include: 指定头文件搜索路径为 mathlib/include,编译器会在此目录下查找 add.h 文件。

  • -L./mathlib/lib: 指定库文件搜索路径为 mathlib/lib,链接器会在此目录下查找 libcal.a 静态库文件。

  • -lcal: 链接名为 libcal.a 的静态库文件(-l 参数表示链接库文件,cal 是库文件的简称,编译器会自动补全为 libcal.a)。

  • -o main: 指定生成的可执行文件名为 main

3. 方法二:将头文件和库文件拷贝到系统路径

        如果希望在系统中任何地方都能直接使用该库,可以将头文件和库文件拷贝到系统的搜索路径,例如:

# 拷贝头文件到 `/usr/include`
sudo cp ./mathlib/include/add.h /usr/include/
sudo cp ./mathlib/include/sub.h /usr/include/

# 拷贝库文件到 `/usr/lib`
sudo cp ./mathlib/lib/libcal.a /usr/lib/

后续编译命令: 拷贝完成后,无需指定 -I-L 路径,直接使用以下命令编译即可:

gcc main.c -lcal -o main

解释:

  • /usr/include 是系统默认的头文件搜索路径。

  • /usr/lib 是系统默认的库文件搜索路径。

总结:

  • 方法一 是一种更灵活的方式,适合在一个项目中使用,并且不修改系统文件。

  • 方法二 是一种全局的方式,适合将库文件提供给系统中的多个项目使用,但需要谨慎操作,避免冲突。

五、动态库的打包与使用

5.1 打包动态库

1. 生成位置无关的对象文件

启用 -fPIC 选项编译源文件,生成位置无关代码的目标文件:

gcc -fPIC -c add.c sub.c

结果会生成 add.osub.o 两个位置无关代码的目标文件。

2. 生成动态库

使用以下命令将目标文件链接为共享动态库:

gcc -shared -o libcal.so add.o sub.o

-shared 选项用于生成共享库文件,-o libcal.so 指定生成的动态库文件名为 libcal.so

3. 组织头文件和库文件

将相关文件放入指定目录,最终的文件夹结构如下:

  • include/ 目录存放头文件 add.hsub.h

  • lib/ 目录存放生成的动态库文件 libcal.so

5.2 使用动态库

1. 创建主程序 main.c(同前)

#include <stdio.h>
#include "add.h"

int main() {
    int x = 20, y = 10, z = add(x, y);
    printf("%d + %d = %d\n", x, y, z);
    return 0;
}

2. 使用编译选项

编译命令如下:

gcc main.c -I./mlib/include -L./mlib/lib -lcal -o main

参数解释:

  • -I./mlib/include: 指定头文件搜索路径为 mlib/include

  • -L./mlib/lib: 指定库文件搜索路径为 mlib/lib

  • -lcal: 链接动态库 libcal.so

  • -o main: 指定生成的可执行文件名为 main

但是与静态库的使用不同的是,此时我们生成的可执行程序并不能直接运行。

因为此时可执行程序所依赖的动态库是没有被找到的,我们可以使用ldd命令进行查看。

3. 解决动态库运行时依赖问题

方式一:将动态库拷贝到系统路径

将动态库文件拷贝到系统的共享库路径(如 /lib64/usr/lib64):

sudo cp mlib/lib/libcal.so /lib64/

注意事项:

  • 使用 /lib64/usr/lib64 需要管理员权限。

  • 不建议随意改动系统库路径,可能会引发系统兼容性问题。

方式二:设置 LD_LIBRARY_PATH

在当前终端会话中,通过以下命令设置动态库路径:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/mlib/lib

或将路径永久添加到环境变量(编辑 ~/.bashrc~/.bash_profile 文件):

echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/mlib/lib' >> ~/.bashrc
source ~/.bashrc

方式三:更新动态链接器缓存

创建自定义的动态链接器配置文件(例如 /etc/ld.so.conf.d/mylib.conf):

echo "/path/to/mlib/lib" > /etc/ld.so.conf.d/mylib.conf
sudo ldconfig

步骤:

  1. 创建 /etc/ld.so.conf.d/mylib.conf 文件,内容为 /path/to/mlib/lib

  2. 运行 sudo ldconfig 更新动态链接器缓存。


总结

1. 动态库需要在运行时加载,因此需要确保动态库路径设置正确。

2. 方法一(使用 -I-L 编译选项)适用于开发和测试阶段。

3. 方法二(设置动态库路径)适用于部署环境,其中:

  • 拷贝到系统库路径是最直接的方式,但需谨慎操作。

  • 设置 LD_LIBRARY_PATH 是临时且灵活的方式。

  • 更新动态链接器缓存是最规范的方式,适合长期使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南风与鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值