Linux动静态库开发基础:静态库与动态库的编译构建、链接使用及问题排查

文 章 目 录

  • [一、静 态 库](#一、静 态 库)
    • [1、背 景](#1、背 景)
    • [2、原 理](#2、原 理)
    • [3、静 态 库 的 流 程](#3、静 态 库 的 流 程)
      • [(1)编 写 者](#(1)编 写 者)
      • [(2)使 用 者](#(2)使 用 者)
      • [(3)简 化 gcc 编 译 选 项](#(3)简 化 gcc 编 译 选 项)
      • [(4)myerrno 问 题](#(4)myerrno 问 题)
      • [(5)结 论](#(5)结 论)
  • [二、动 静 态 库](#二、动 静 态 库)
    • [1、编 写 者](#1、编 写 者)
    • [2、使 用 者](#2、使 用 者)
    • [3、加 载 找 不 到 动 态 库](#3、加 载 找 不 到 动 态 库)
  • [三、总 结](#三、总 结)

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 Linux。

💡个 人 主 页:@笑口常开xpr 的 个 人 主 页

📚系 列 专 栏:Linux 探 索 之 旅:从 命 令 行 到 系 统 内 核

✨代 码 趣 语:静 态 库 是 装 满 工 具 的 箱 子,编 译 时 全 塞 程 序,方 便 但 沉;动 态 库 是 共 享 架,记 位 置,没 工 具 就 卡 壳。

💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。

📦gitee 链 接:gitee

在 Linux C/C++ 开 发 中,库 是 代 码 复 用 和 工 程 化 的 核 心。不 少 开 发 者 会 遇 到 源 码 泄 露、编 译 "找 不 到 头 文 件 / 库"、动 态 库 运 行 加 载 失 败 等 问 题,本 质 是 对 库 的 流 程 不 熟 悉。本 文 从 背 景 切 入,先 讲 静 态 库 的 原 理、制 作 与 使 用,再 讲 动 态 库 的 实 战 技 巧,帮 你 掌 握 库 的 全 流 程 应 用。


一、静 态 库

1、背 景

设 计 一 个 静 态 库 并 将 已 经 写 好 的 代 码 给 别 人 用。有 两 种 方 法:

  1. 把 源 文 件 给 他
  2. 把 源 代 码 打 包 成 库,必 须 提 供 头 文 件。头 文 件 的 本 质 是 库 文 件 的 说 明 书。

libxxx.a - - - 静 态 链 接

libxxx.so - - - 动 态 链 接


2、原 理


3、静 态 库 的 流 程

(1)编 写 者

  1. 编 写 源 代 码(不 包 括 main 函 数)。

mymath.h

javascript 复制代码
#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

javascript 复制代码
#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

javascript 复制代码
lib=libmymath.a
$(lib):mymath.o
	ar -rc $@ $^
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

这 里 不 写 $@ 的 原 因 是 因 为 gcc 可 以 将 mymath.h 编 译 成 和 源 文 件 名 字 相 同 的 .o 文 件。

ar 是 生 成 静 态 库 的 1 个 命 令,可 以 将 所 有 的 .o 文 件 打 包 形 成 .a 文 件,-rc 表 示 将 所 有 的 .o 放 在 目 标 文 件 .a 中,如 果 不 存 在 就 创 建,如 果 存 在 就 替 换。

  1. 编 译 生 成 .a.o 文 件。
  2. 将 生 成 的 文 件 打 包 进 文 件 夹 中。
  3. 将 生 成 的 lib 文 件 夹 打 包 压 缩。

(2)使 用 者

  1. 下 载 并 解 压 静 态 库 的 压 缩 包
  2. 编 写 main 函 数。
javascript 复制代码
#include "mymath.h"
int main()
{
    int n = div(10,0);
    printf("10/0=%d,errno=%d\n",n,myerrno);
    return 0;
}
  1. 编 译 代 码

没 有 找 到 头 文 件

原 因

编 译 器 会 在 默 认 路 径 和 当 前 目 录 下(和 源 代 码 在 同 一 级 路 径 下) 寻 找 头 文 件。

解 决 方 法
方 法 1
gcc main.c -I + 目 录:编 译 器 会 去 指 定 目 录 下 寻 找 头 文 件。
方 法 2
#include "lib/include/mymath.h" 可 以 在 代 码 中 包 含 路 径。

这 里 推 荐 使 用 方 法 1。


链 接 错 误(找 不 到 静 态 库)

下 图 中 的 错 误 是 链 接 错 误,原 因 是 因 为 以 .o 结 尾 的 一 般 是 链 接 错 误。

-c:编 译 到 目 标 代 码

gcc -c 编 译 通 过,只 能 说 明 代 码 在 语 法 和 基 本 编 译 规 则 上 没 有 问 题,它 不 检 查 链 接 错 误。

-L

静 态 库 的 存 储 路 径。

没 有 包 含 .a 文 件

-l

找 到 .a 文 件,这 里 是 静 态 库 的 真 实 名 字,建 议 -l 和 库 的 真 实 名 字 相 连 接,二 者 之 间 没 有 空 格。

库 的 真 实 名 字

去 掉 前 缀 和 后 缀。



(3)简 化 gcc 编 译 选 项

  1. 直 接 将 头 文 件 和 静 态 库 放 进 系 统 文 件 夹。这 是 库 的 安 装。这 里 不 建 议 不 要 将 头 文 件 和 静 态 库 放 入 系 统 中。

    需 要 指 明 静 态 库 的 真 实 名 字,才 能 编 译 成 功。
  2. 使 用 软 链 接

  3. 使 用 makefile
javascript 复制代码
main:main.c
	gcc 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。
修 改 后 的 代 码

javascript 复制代码
#include "mymath.h"
int main()
{
    int n = div(10,0);
    printf("10/0=%d,errno=%d\n",n,myerrno);
    return 0;
}

(5)结 论

  1. 第 3 方 库 使 用 的 时 候 必 须 使 用 -l 选 项

  2. ldd

    可 以 查 看 是 否 是 动 态 链 接 还 是 静 态 链 接。如 下 图 gcc 采 用 的 是 动 态 链 接,因 为 文 件 是 以 .so 结 尾 的。

  3. gcc 默 认 是 动 态 链 接,如 果 只 提 供 静 态 链 接,gcc 只 能 对 该 库 使 用 静 态 链 接。ldd 的 输 出 反 映 的 是 整 个 程 序 是 否 依 赖 动 态 库,而 非 单 个 库 的 链 接 方 式。上 面 使 用 的 是 静 态 库,gcc 对 静 态 库 采 用 静 态 链 接,这 里 没 有 显 示 静 态 库 的 链 接。但 对 可 执 行 程 序 来 讲,程 序 中 还 有 其 他 库(如 C 标 准 库 libc)使 用 了 动 态 链 接(默 认 行 为),整 个 程 序 仍 然 是 "动 态 链 接 的 可 执 行 文 件"。

  4. 当 同 一 库 的 动 态 版 本(.so)和 静 态 版 本(.a)同 时 存 在 时,gcc 默 认 会 优 先 选 择 动 态 链 接 方 式。

  5. 若 要 强 制 使 用 静 态 链 接,可 在 编 译 时 添 加 -static 选 项。不 过 需 要 注 意,-static 并 非 绝 对 强 制 的 指 令 - - - 它 的 作 用 是 告 知 gcc 尽 量 采 用 静 态 链 接,但 在 某 些 情 况 下,gcc 可 能 仍 无 法 实 现 完 全 的 静 态 链 接。


二、动 静 态 库

1、编 写 者

  1. 编 写 代 码
    gcc

mylog.h

javascript 复制代码
#pragma once //防止头文件被重复包含
#include<stdio.h>
void log(const char*);

mylog.c

javascript 复制代码
#include"mylog.h"
void log(const char* info)
{
    printf("Warning:%s\n",info);
}

myprint.c

javascript 复制代码
#include "myprint.h"
void Print()
{
    printf("hello world!\n");
    printf("hello world!\n");
    printf("hello world!\n");
    printf("hello world!\n");
}

myprint.h

javascript 复制代码
#pragma once //防止头文件被重复包含
#include<stdio.h>
void Print();

makefile

javascript 复制代码
dy-lib=libmymethod.so
static-lib=libmymath.a

# 同时生成动态库和静态库
.PHONY:all
all: $(dy-lib) $(static-lib)


# 生成静态库
$(static-lib):mymath.o
	ar -rc $@ $^
mymath.o:mymath.c
	gcc -c $^

# 生成动态库
$(dy-lib):mylog.o myprint.o
	gcc -shared -o $@ $^
mylog.o:mylog.c
	gcc -fPIC -c $^
myprint.o:myprint.c
	gcc -fPIC -c $^

# 清理静态库文件
.PHONY:clean
clean:
	rm -rf *.o *.a *.so mylib

# 打包
.PHONY:output
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.a mylib/lib 
	cp *.so mylib/lib
  1. 编 译 代 码,将 .c 文 件 编 译 成 .o 文 件。
    fPIC :产 生 位 置 无 关 码。
  2. 生 成 动 态 库
    .so 表 示 动 态 库。
    -shared 此 选 项 表 示 不 生 成 可 执 行 程 序,将 尽 量 使 用 动 态 库,所 以 生 成 文 件 比 较 小,但 是 需 要 系 统 由 动 态 库 -O0、-O1、-O2、-O3 编 译 器 的 优 化 选 项 的 4 个 级 别,-O0 表 示 没 有 优 化,-O1 为 缺 省 值,-O3 优 化 级 别 最 高。

2、使 用 者

  1. 编 译 代 码(包 含 动 静 态 库)
  2. 运 行 代 码



    有 ldd 和 a.out 可 以 看 出 生 成 的 可 执 行 程 序 为 动 态 链 接 的。
    gcc 编 译 时 的 路 径 是 编 译 器,还 需 要 让 系 统(加 载 器) 明 白 动 态 库 在 哪 里。

3、加 载 找 不 到 动 态 库

  1. 将 动 态 库 拷 贝 到 系 统 路 径(/lib64 或 者 /usr/lib64/) 下。实 际 情 况,我 们 使 用 的 库 都 是 别 人 成 熟 的 库,都 采 用 直 接 安 装 到 系 统 的 方 式。

  2. 建 立 软 链 接

  3. 将 自 己 的 库 所 在 的 路 径 添 加 到 系 统 的 环 境 变 量 中。使 用 echo $LD_LIBRARY_PATH 搜 索 用 户 自 定 义 的 库 路 径。

    xshell 每 次 重 启 都 会 重 新 加 载 环 境 变 量,可 以 将 LD_LIBRARY_PATH 这 个 环 境 变 量 添 加 到 vim ~/.bash_profile,来 解 决 这 个 问 题。

  4. /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 种 加 载 问 题 解 决 方 案)的 核 心 内 容。静 态 库 嵌 入 程 序、独 立 但 体 积 大,动 态 库 共 享 模 块、轻 量 但 需 依 赖 系 统,需 按 需 选 择。后 续 可 尝 试 封 装 通 用 模 块 为 库,或 研 究 版 本 管 理,进 一 步 提 升 开 发 效 率。

相关推荐
艾莉丝努力练剑2 小时前
【C++】类和对象(下):初始化列表、类型转换、Static、友元、内部类、匿名对象/有名对象、优化
linux·运维·c++·经验分享
风_峰3 小时前
PuTTY软件访问ZYNQ板卡的Linux系统
linux·服务器·嵌入式硬件·fpga开发
数智顾问3 小时前
从ENIAC到Linux:计算机技术与商业模式的协同演进——云原生重塑闭源主机,eBPF+WebAssembly 双引擎的“Linux 内核即服务”实践
linux
-SGlow-3 小时前
Linux相关概念和易错知识点(45)(网络层、网段划分)
linux·运维·服务器·网络
三体世界3 小时前
测试用例全解析:从入门到精通(1)
linux·c语言·c++·python·功能测试·测试用例·测试覆盖率
过尽漉雪千山3 小时前
Flink1.17.0集群的搭建
java·大数据·linux·flink·centos
csdn_aspnet3 小时前
Windows、Linux 系统 nodejs 和 npm 版本更新及错误修复
linux·windows·npm·node.js
程序员东岸3 小时前
C语言入门指南:字符函数和字符串函数
c语言·笔记·学习·程序人生·算法
潘潘潘潘潘潘潘潘潘潘潘潘4 小时前
【MySQL】从零开始学习MySQL:基础与安装指南
linux·运维·服务器·数据库·学习·mysql