Linux动静态库开发基础:静态库与动态库的编译构建、链接使用及问题排查
文 章 目 录
- 一、静 态 库
- 1、背 景
- 2、原 理
- 3、静 态 库 的 流 程
- (1)编 写 者
- (2)使 用 者
- (3)简 化 gcc 编 译 选 项
- (4)myerrno 问 题
- (5)结 论
- 二、动 静 态 库
- 1、编 写 者
- 2、使 用 者
- 3、加 载 找 不 到 动 态 库
- 三、总 结
💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 Linux。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:Linux 探 索 之 旅:从 命 令 行 到 系 统 内 核
✨代 码 趣 语:静 态 库 是 装 满 工 具 的 箱 子,编 译 时 全 塞 程 序,方 便 但 沉;动 态 库 是 共 享 架,记 位 置,没 工 具 就 卡 壳。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee
在 Linux C/C++ 开 发 中,库 是 代 码 复 用 和 工 程 化 的 核 心。不 少 开 发 者 会 遇 到 源 码 泄 露、编 译 “找 不 到 头 文 件 / 库”、动 态 库 运 行 加 载 失 败 等 问 题,本 质 是 对 库 的 流 程 不 熟 悉。本 文 从 背 景 切 入,先 讲 静 态 库 的 原 理、制 作 与 使 用,再 讲 动 态 库 的 实 战 技 巧,帮 你 掌 握 库 的 全 流 程 应 用。
一、静 态 库
1、背 景
设 计 一 个 静 态 库 并 将 已 经 写 好 的 代 码 给 别 人 用。有 两 种 方 法:
- 把 源 文 件 给 他
- 把 源 代 码 打 包 成 库,必 须 提 供 头 文 件。头 文 件 的 本 质 是 库 文 件 的 说 明 书。
libxxx.a - - - 静 态 链 接
libxxx.so - - - 动 态 链 接
2、原 理
3、静 态 库 的 流 程
(1)编 写 者
- 编 写 源 代 码(不 包 括 main 函 数)。
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;
}
makefile
lib=libmymath.a
$(lib):mymath.oar -rc $@ $^
mymath.o:mymath.cgcc -c $^
.PHONY:clean
clean:rm -rf *.o *.a lib
.PHONY:output
output:mkdir -p lib/includemkdir -p lib/mymathlibcp *.h lib/includecp *.a lib/mymathlib
这 里 不 写 $@
的 原 因 是 因 为 gcc 可 以 将 mymath.h
编 译 成 和 源 文 件 名 字 相 同 的 .o
文 件。
ar
是 生 成 静 态 库 的 1 个 命 令,可 以 将 所 有 的 .o
文 件 打 包 形 成 .a
文 件,-rc
表 示 将 所 有 的 .o
放 在 目 标 文 件 .a
中,如 果 不 存 在 就 创 建,如 果 存 在 就 替 换。
- 编 译 生 成
.a
和.o
文 件。
- 将 生 成 的 文 件 打 包 进 文 件 夹 中。
- 将 生 成 的
lib
文 件 夹 打 包 压 缩。
(2)使 用 者
- 下 载 并 解 压 静 态 库 的 压 缩 包
- 编 写 main 函 数。
#include "mymath.h"
int main()
{int n = div(10,0);printf("10/0=%d,errno=%d\n",n,myerrno);return 0;
}
- 编 译 代 码
没 有 找 到 头 文 件
原 因
编 译 器 会 在 默 认 路 径 和 当 前 目 录 下(和 源 代 码 在 同 一 级 路 径 下) 寻 找 头 文 件。
解 决 方 法
方 法 1
gcc main.c -I + 目 录
:编 译 器 会 去 指 定 目 录 下 寻 找 头 文 件。
方 法 2
#include "lib/include/mymath.h"
可 以 在 代 码 中 包 含 路 径。
这 里 推 荐 使 用 方 法 1。
链 接 错 误(找 不 到 静 态 库)
下 图 中 的 错 误 是 链 接 错 误,原 因 是 因 为 以 .o
结 尾 的 一 般 是 链 接 错 误。
-c:编 译 到 目 标 代 码
gcc -c 编 译 通 过,只 能 说 明 代 码 在 语 法 和 基 本 编 译 规 则 上 没 有 问 题,它 不 检 查 链 接 错 误。
-L
静 态 库 的 存 储 路 径。
没 有 包 含 .a 文 件
-l
找 到 .a
文 件,这 里 是 静 态 库 的 真 实 名 字,建 议 -l 和 库 的 真 实 名 字 相 连 接,二 者 之 间 没 有 空 格。
库 的 真 实 名 字
去 掉 前 缀 和 后 缀。
(3)简 化 gcc 编 译 选 项
- 直 接 将 头 文 件 和 静 态 库 放 进 系 统 文 件 夹。这 是 库 的 安 装。这 里 不 建 议 不 要 将 头 文 件 和 静 态 库 放 入 系 统 中。
需 要 指 明 静 态 库 的 真 实 名 字,才 能 编 译 成 功。
- 使 用 软 链 接
- 使 用 makefile
main:main.cgcc main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymath -o main
.PHONY:clean
clean:rm -f main
(4)myerrno 问 题
上 面 的 errno 不 是 -1 的 原 因 是 因 为 c 语 言 的 实 例 化 是 从 右 向 左 实 例 化 的。即 printf("10/0=%d,errno=%d\n",div(10,0),myerrno);
这 句 代 码 中 先 调 用 myerrno 然 后 使 用 div。调 用 的 顺 序 错 误 正 确 的 顺 序 是 在 调 用 myerrno 时 应 使 用 div,然 后 使 用 myerrno。
修 改 后 的 代 码
#include "mymath.h"
int main()
{int n = div(10,0);printf("10/0=%d,errno=%d\n",n,myerrno);return 0;
}
(5)结 论
-
第 3 方 库 使 用 的 时 候 必 须 使 用
-l
选 项 -
ldd
可 以 查 看 是 否 是 动 态 链 接 还 是 静 态 链 接。如 下 图 gcc 采 用 的 是 动 态 链 接,因 为 文 件 是 以.so
结 尾 的。
-
gcc 默 认 是 动 态 链 接,如 果 只 提 供 静 态 链 接,gcc 只 能 对 该 库 使 用 静 态 链 接。ldd 的 输 出 反 映 的 是 整 个 程 序 是 否 依 赖 动 态 库,而 非 单 个 库 的 链 接 方 式。上 面 使 用 的 是 静 态 库,gcc 对 静 态 库 采 用 静 态 链 接,这 里 没 有 显 示 静 态 库 的 链 接。但 对 可 执 行 程 序 来 讲,程 序 中 还 有 其 他 库(如 C 标 准 库 libc)使 用 了 动 态 链 接(默 认 行 为),整 个 程 序 仍 然 是 “动 态 链 接 的 可 执 行 文 件”。
-
当 同 一 库 的 动 态 版 本(.so)和 静 态 版 本(.a)同 时 存 在 时,gcc 默 认 会 优 先 选 择 动 态 链 接 方 式。
-
若 要 强 制 使 用 静 态 链 接,可 在 编 译 时 添 加 -static 选 项。不 过 需 要 注 意,-static 并 非 绝 对 强 制 的 指 令 - - - 它 的 作 用 是 告 知 gcc 尽 量 采 用 静 态 链 接,但 在 某 些 情 况 下,gcc 可 能 仍 无 法 实 现 完 全 的 静 态 链 接。
二、动 静 态 库
1、编 写 者
- 编 写 代 码
gcc
mylog.h
#pragma once //防止头文件被重复包含
#include<stdio.h>
void log(const char*);
mylog.c
#include"mylog.h"
void log(const char* info)
{printf("Warning:%s\n",info);
}
myprint.c
#include "myprint.h"
void Print()
{printf("hello world!\n");printf("hello world!\n");printf("hello world!\n");printf("hello world!\n");
}
myprint.h
#pragma once //防止头文件被重复包含
#include<stdio.h>
void Print();
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):mylog.o myprint.ogcc -shared -o $@ $^
mylog.o:mylog.cgcc -fPIC -c $^
myprint.o:myprint.cgcc -fPIC -c $^# 清理静态库文件
.PHONY:clean
clean:rm -rf *.o *.a *.so mylib# 打包
.PHONY:output
output:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.a mylib/lib cp *.so mylib/lib
- 编 译 代 码,将 .c 文 件 编 译 成 .o 文 件。
fPIC:产 生 位 置 无 关 码。
- 生 成 动 态 库
.so
表 示 动 态 库。
-shared
此 选 项 表 示 不 生 成 可 执 行 程 序,将 尽 量 使 用 动 态 库,所 以 生 成 文 件 比 较 小,但 是 需 要 系 统 由 动 态 库 -O0、-O1、-O2、-O3 编 译 器 的 优 化 选 项 的 4 个 级 别,-O0 表 示 没 有 优 化,-O1 为 缺 省 值,-O3 优 化 级 别 最 高。
2、使 用 者
- 编 译 代 码(包 含 动 静 态 库)
- 运 行 代 码
有 ldd 和 a.out 可 以 看 出 生 成 的 可 执 行 程 序 为 动 态 链 接 的。
gcc 编 译 时 的 路 径 是 编 译 器,还 需 要 让 系 统(加 载 器) 明 白 动 态 库 在 哪 里。
3、加 载 找 不 到 动 态 库
-
将 动 态 库 拷 贝 到 系 统 路 径(/lib64 或 者 /usr/lib64/) 下。实 际 情 况,我 们 使 用 的 库 都 是 别 人 成 熟 的 库,都 采 用 直 接 安 装 到 系 统 的 方 式。
-
建 立 软 链 接
-
将 自 己 的 库 所 在 的 路 径 添 加 到 系 统 的 环 境 变 量 中。使 用
echo $LD_LIBRARY_PATH
搜 索 用 户 自 定 义 的 库 路 径。
xshell 每 次 重 启 都 会 重 新 加 载 环 境 变 量,可 以 将LD_LIBRARY_PATH
这 个 环 境 变 量 添 加 到vim ~/.bash_profile
,来 解 决 这 个 问 题。
-
在
/etc/ld.so.conf.d
这 是 系 统 维 护 动 态 库 时 放 的 路 径,建 立 自 己 的 动 态 库 路 径 的 配 置 文 件,然 后 使 用ldconfig
重 新 加 载 即 可。
(1)切 换 到 root 身 份 并 进 入 到/etc/ld.so.conf.d
这 个 路 径 下,创 建 以.conf
结 尾 的 文 件 并 添 加 路 径,这 种 方 法 和 xshell 是 否 关 闭 没 有 影 响。
(2)使 用ldconfig
重 新 加 载 文 件
(3)成 功 执 行 可 执 行 程 序
(4)如 果 不 想 使 用 这 种 方 法,可 以 删 除 刚 才 新 建 的 文 件,然 后 使 用ldconfig
重 新 加 载 文 件 即 可。
三、总 结
本 文 覆 盖 了 静 态 库(.o 打 包、Makefile 构 建、-I/-L/-l 链 接)与 动 态 库(-fPIC/-shared 编 译、4 种 加 载 问 题 解 决 方 案)的 核 心 内 容。静 态 库 嵌 入 程 序、独 立 但 体 积 大,动 态 库 共 享 模 块、轻 量 但 需 依 赖 系 统,需 按 需 选 择。后 续 可 尝 试 封 装 通 用 模 块 为 库,或 研 究 版 本 管 理,进 一 步 提 升 开 发 效 率。