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

相关推荐
虾..17 小时前
Linux 软硬链接和动静态库
linux·运维·服务器
Evan芙17 小时前
Linux常见的日志服务管理的常见日志服务
linux·运维·服务器
晨晖218 小时前
单链表逆转,c语言
c语言·数据结构·算法
hkhkhkhkh12319 小时前
Linux设备节点基础知识
linux·服务器·驱动开发
HZero.chen20 小时前
Linux字符串处理
linux·string
张童瑶20 小时前
Linux SSH隧道代理转发及多层转发
linux·运维·ssh
汪汪队立大功12321 小时前
什么是SELinux
linux
石小千21 小时前
Linux安装OpenProject
linux·运维
柏木乃一21 小时前
进程(2)进程概念与基本操作
linux·服务器·开发语言·性能优化·shell·进程
Lime-309021 小时前
制作Ubuntu 24.04-GPU服务器测试系统盘
linux·运维·ubuntu