Linux系统:动静态库的制作与安装

本节重点

  • 库的概念与分类
  • 静态库的创建与使用
  • 动态库的创建于使用
  • 安装库到系统
  • 动静态库的区别与联系

一、什么是库

在Linux中,库(Library)是一组预编译的函数、类或数据结构的集合,供程序在运行时或编译时调用。库的主要目的是提供可重用的代码,减少开发者的工作量,并提高代码的模块化和可维护性。

本质上,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种形式:

  • 静态库:.a [ Linux ],.lib [windows]
  • 动态库:.so [Linux],.dll [windows]

库的包装

为了更高效地管理我们的自定义库,通常需要创建一个库目录,其中包括include与mylib两个子目录,将.h文件和.a文件分别放在两个子目录中:

此时我们就可以将lib压缩成安装包发送给不同的用户,用户就可以解压安装后使用我们的第三方库了。

二、静态库

静态库(Static Library)在编译时被链接到程序中,生成的可执行文件包含了库中的所有代码。静态库的文件扩展名通常为 .a(Archive)。使用静态库的程序在运行时不需要依赖外部库文件,但会导致可执行文件体积较大。

2.1 创建静态库

静态库通常是一组编译好的目标文件(.o.obj)的集合,可以通过归档工具(如 ar)打包成一个库文件(.a.lib),而.h文件则声明了库中函数具体的使用方法。

创建静态库包括以下步骤:

  • 编写源代码(例如 .c)文件
  • 将源代码编译为目标( .o)文件
  • 使用归档工具(ar)将目标文件打包成静态库

下面我们创建一个自己的静态库:

编写源代码

首先我们编写自己的源代码文件,这里我们将方法的声明写在.h文件中,方法的实现写在.c文件中,这样做的好处是之后我们创建库方法后,用户可以根据.h文件中方法的声明正确调用库方法。

cpp 复制代码
//Myadd.h
int mysub(int c,int d);
//Mysub.h
int myadd(int a,int b);
//Myadd.c
#include"Myadd.h"

int myadd(int a,int b)
{
    return a+b;
}
//Mysub.c
#include"Mysub.h"

int mysub(int c,int d)
{
    return c-d;
}

之后我们利用以下命令将所有源文件编译形成同名.o文件:

bash 复制代码
gcc -c *.c

利用归档工具(ar命令)生成静态库:

ar(Archiver)命令是 Unix/Linux 系统中用于创建、修改和提取静态库(归档文件)的核心工具,尤其在 C/C++ 开发中用于管理 .a 格式的静态库文件。

静态库的命名一般是以lib开头以.a结尾,而静态库真正的名字是去掉开头lib与结尾.a后剩余的字符。例如:一个静态库为libmystr.a则静态库的名字为mystr。

下面我们使用以下命令来将上述生成的所有.o文件链接生成静态库libmyc.a:

bash 复制代码
ar -rc mylib.a *.o

其中

  • -c:创建归档文件(若不存在)。
  • -r:插入或替换文件(若已存在则替换)。

更多选项的详解可以参考下表:

选项 描述
-r 插入或替换文件(若已存在则替换)。
-c 创建归档文件(若不存在)。
-s 生成符号索引(等价于 ranlib)。
-d 删除指定文件。
-m 移动文件位置(需指定目标文件)。
-x 提取文件。
-t 列出归档内容。
-v 显示详细操作信息。
-q 快速追加文件(不检查重复,不排序)。
-u 仅当文件比归档中的版本新时替换。

包装静态库:

bash 复制代码
mkdir lib
mkdir lib/include
mkdir lib/mylibc
cp ./*.h ./lib/include
cp ./*.a ./lib/mylibc

2.2 静态库的使用

错误示例:

从上述创建静态库的过程我们不免看出,其实静态库本质上就是.o文件的集合,后续我们需要调用其中的库方法时只需要将我们的源代码编译形成.o文件后与静态库链接就可以形成可执行文件。

但是在使用我们的自定义静态库时会出现许多错误导致链接出错,以下是我们在链接过程中可能会出现的错误与解决方法:

这里我们将源文件code.c编译形成同名.o与静态库链接看是否可以形成可执行文件:

cpp 复制代码
//code.c
#include<stdio.h>
#include"Myadd.h"
#include"Mysub.h"
int main()
{
    int a=5,b=4;
    printf("%d\n",myadd(a,b));
    printf("%d\n",mysub(a,b));
    return 0;
}
bash 复制代码
//形成同名.o
gcc -c code.c
//形成可执行
gcc -o code code.o

此时会出现以下报错:

这里由于我们链接的是第三方库libmyc.a且没有指明库的名称和路径,导致code.c中调用的库方法无法被gcc编译器识别报出方法未定义之类的错误。

3.2.1 指令操作

如果我们要链接任何非C/C++标准库的第三方库都必须指明第三方库的名称与路径。我们可以通过以下指令来告诉编译器:

bash 复制代码
gcc -o 可执行文件 .c/.o文件 -I头文件路径 -L库路径 -l库名称

-I选项:

  • 用于指定头文件的搜索路径。
  • 示例:-I/path/to/third_party/include
  • 作用:帮助编译器找到第三方库的头文件,确保代码中的函数和类型声明能被正确解析。

-L选项:

  • 用于指定动态库的搜索路径。
  • 示例:-L/path/to/third_party/lib
  • 作用:帮助链接器找到动态库文件(.so),在链接阶段解析符号引用。

-l选项:

  • 用于指定要链接的库名称。
  • 示例:-lthird_party
  • 作用:链接名为 libthird_party.so 的动态库(省略 lib 前缀和 .so 后缀)。

例如:

bash 复制代码
gcc -o code code.o -I. -L. -lmyc

表示gcc编译器需要到当前目录寻找头文件和libmyc.a将code.o文件形成code。

此时就可以成功形成可执行文件了:

3.2.2 安装静态库到系统

将静态库文件复制到系统库目录(如 /usr/local/lib)。通常建议使用 /usr/local/lib 而非 /usr/lib,以避免干扰系统自带的库。

bash 复制代码
sudo cp 静态库路径 /usr/local/lib/

三、动态库

动态库(Dynamic Library 或 Shared Library)在程序运行时被加载,多个程序可以共享同一个动态库的代码。动态库的文件扩展名通常为 .so(Shared Object)。使用动态库可以减少可执行文件的大小,但程序在运行时需要依赖相应的库文件。

3.1 创建动态库

首先与静态库类似,我们需要准备源代码:头文件声明库提供的函数与变量,实现文件则包含这些函数和变量的具体实现。

cpp 复制代码
//Myadd.h
int mysub(int c,int d);
//Mysub.h
int myadd(int a,int b);
//Myadd.c
#include"Myadd.h"

int myadd(int a,int b)
{
    return a+b;
}
//Mysub.c
#include"Mysub.h"

int mysub(int c,int d)
{
    return c-d;
}

编译生成位置无关码(.o文件)

在gcc编译器中使用-fPIC选项生成与位置无关码:

bash 复制代码
gcc -fPIC -c Myadd.c -o Myadd.o
gcc -fPIC -c Mysub.c -o Mysub.o
  • -fPIC:生成位置无关代码(Position Independent Code),这是动态库必需的。

创建动态库:

使用gcc编译器中的-shared选项将目标文件打包成动态库:

bash 复制代码
gcc -shared -o libmyc.so *.o

包装动态库:

bash 复制代码
mkdir lib_so
mkdir lib_so/include
mkdir lib_so/mylibc
cp ./*.h ./lib_so/include
cp ./*.so ./lib_so/mylibc

3.2 动态库的使用

在程序编译链接动态库时会发生一个奇怪的现象,当我们使用-I,-L,-l分别说明头文件与动态库的路径还有动态库名称给gcc编译器时,编译器会正常运行将.c文件编译形成可执行,但是当我们运行可执行文件时就会出现所依赖动态库缺失之类的链接错误:

问题出在我们提供给gcc的路径信息只是临时的。虽然gcc在编译时能找到头文件和动态库,并成功生成可执行文件,但在运行时动态加载器却无法定位这些库文件。这是因为我们提供的路径信息只在编译阶段有效,而运行时动态加载器只会在系统默认路径中查找库文件。由于我们没有将动态库安装到系统目录,导致加载器找不到依赖库,从而出现错误。

3.2.1 安装动态库到系统

方法一:将库文件复制到系统库目录

bash 复制代码
sudo cp 动态库路径 /usr/local/lib/
sudo ldconfig
  • ldconfig:更新动态库缓存,使系统能识别新库。

方法二:临时设置 LD_LIBRARY_PATH

bash 复制代码
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:动态库路径

例如:

bash 复制代码
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/home/yjh/linux-learning/5_26
/lib_so/mylibc
  • 注意:此方法仅在当前终端会话中有效。

方法三:永久设置 LD_LIBRARY_PATH

将以下行添加到 ~/.bashrc~/.bash_profile 文件中:

bash 复制代码
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:动态库路径

然后执行:

bash 复制代码
source ~/.bashrc
  • 注意 :修改后需重新登录或执行 source 命令生效。

方法 4:修改 /etc/ld.so.conf.d/ 配置

在**/etc/ld.so.conf.d/**路径下创建一个.conf文件,然后打开创建的.conf文件将动态库的路径写入。

例如:

创建一个 mylib.conf文件并添加库路径:

bash 复制代码
sudo touch /etc/ld.so.conf.d/mylib.conf

然后将自己的动态库路径写入mylib.conf后执行:

bash 复制代码
sudo ldconfig

更新动态库缓存

四、动静态库的区别

特性 静态库(.a 动态库(.so
链接时机 编译时链接,库代码直接嵌入到可执行文件中。 运行时链接,库代码在程序启动时或运行时动态加载。
文件大小 可执行文件较大(包含库代码)。 可执行文件较小(仅包含库的引用)。
磁盘/内存占用 每个程序有独立的库代码副本,占用更多磁盘空间;运行时内存占用可能更小。 多个程序共享同一份库文件,节省磁盘空间;但运行时可能占用更多内存(依赖加载时机)。
更新维护 库更新后需重新编译和链接所有依赖程序。 库更新后无需重新编译程序(需确保接口兼容)。
启动速度 启动较快(库代码已嵌入)。 启动较慢(需加载库文件)。
版本兼容性 编译时即确定库代码,无版本兼容性问题。 需确保运行时库版本与编译时版本兼容,否则可能崩溃。
跨平台/可移植性 代码完全嵌入,可移植性较好(但库代码可能依赖特定平台)。 依赖外部库文件,需确保目标平台有兼容的库版本。
典型使用场景 库代码稳定且不常更新(如数学库、工具函数)。 库代码可能更新(如插件系统、跨平台支持)。

静态库:

  • 库代码稳定且不常更新。
  • 需要程序独立运行(如嵌入式系统)。
  • 避免动态库版本兼容性问题。

动态库:

  • 库代码可能频繁更新。
  • 需要节省磁盘空间或支持插件系统。
  • 多个程序需要共享同一份库代码。
相关推荐
巫山老妖4 分钟前
Linux流量分析:tcpdump&wireshark
linux·嵌入式
秃秃秃秃哇7 分钟前
ubuntu18编译RealSense SDK 2.0
linux
神也佑我橙橙8 分钟前
Ubuntu 22.04 安装英伟达驱动
linux·ubuntu·nvidia
不喝水的鱼儿14 分钟前
Ubuntu 25.04安装搜狗输入法
linux·运维·ubuntu
一只小阿乐36 分钟前
window 服务器上部署前端静态资源以及nginx 配置
运维·服务器·nginx
bjzhang7538 分钟前
docker部署tomcat网站服务器
服务器·docker·tomcat
Jay Kay1 小时前
brpc中后端server挂在同一个命名服务和不同命名服务的区别详解
运维·服务器·brpc
Linux运维技术栈1 小时前
Nginx 动静分离原理与工作机制详解:从架构优化到性能提升
运维·nginx·架构
不脱发的程序猿1 小时前
Linux守护进程
linux·嵌入式
奈斯ing2 小时前
【prometheus+Grafana篇】PromQL核心函数解析:rate()与irate()函数详解
运维·grafana·prometheus