硬核拆解:Linux动态库从原理到实战

本篇目标:

掌握动态库的基本原理及实际应用示例

一.动态库的原理与实战

1.代码准备

文件一:接口定义 my_lib.h

cpp 复制代码
#ifndef MY_LIB_H
#define MY_LIB_H

// 简单的加法,用来演示函数导出
int add(int a, int b);

// 打印库的信息,证明我们在调用动态库
void print_lib_info();

#endif

文件二:库的实现 my_lib.c

cpp 复制代码
#include <stdio.h>
#include "my_lib.h"

int add(int a, int b) 
{
    return a + b;
}

void print_lib_info() 
{
    printf("[Dynamic Library] 这是一个由 C 语言生成的动态库 (.so)\n");
    printf("[Dynamic Library] 它的代码段在内存中是共享的。\n");
}

文件三:调用程序 main.c

cpp 复制代码
#include <stdio.h>
#include "my_lib.h"

int main() 
{
    printf("--- 主程序启动 ---\n");

    print_lib_info();
    
    int result = add(10, 20);
    printf("10 + 20 = %d\n", result);

    printf("--- 主程序结束 ---\n");
    return 0;
}

目前这是在同一个目录下,生成的,如图所示:

为了模拟系统中的动态库,我们还要和之前的静态库一样,形成如图所示的目录:

也就是**./shared_code/lib** 和**./shared_code/usr/include**

2.动态库的形成

先复习一下之前的选项:

-c: 把汇编代码翻译成纯粹的机器码,生成目标文件(.o 文件),它只编译,绝对不进行链接(不去找动态库或静态库)。

-o:作用: 让gcc生成文件的指定的名字。

由于所有的库都是由.o文件组成的,因此我们需要先将想要形成库的.c文件转化为.o文件,但是还

需要一个选项,也就是**-fPIC,作用是生成位置无关代码,让库加载到内存任意地址都能正常运行**

操作如图所示:

然后再将其打上共享标签 ,此时要用**-shared** 选项,封装成 .so 文件操作如图所示:

-shared:告诉链接器生成一个用来被别人动态加载的共享对象(了解即可)

注意:当生成多个.o库文件时,可以将它们统一打包成一个共享库,此时我们可以使用

gcc -fPIC -c *.c 和gcc -shared -o libmyc.so *.o,但是绝对不能把 main 函数的.o文件打包进动态

库里!

这里再提醒一下关于动态库的命名: lib + 库的真实名字 + .so

然后我们在将当前目录下的动态库与库的头文件,拷贝到我们自己创建的目录下,如图所示:

注意:建议使用 cp 命令进行复制操作,而非 mv 命令。若误用 mv 命令,当前目录下的文件会被移

动而非复制(之前犯过的错误)。

在上级目录中,我们查看一下shared_code是否成功有拷贝后的文件,如图所示:

此时我们需要删除code目录下的libmyc.so文件以及除main.c之外的其他文件 ,以免影响后续操作,如图所示:

然后我们在将main.c编译成main.o,如图所示:

可是我们却发现找不到这个my_lib.h文件啊?

原因:当你使用 #include "my_header.h" 时,gcc 默认只会在以下几个地方寻找头文件

  1. 当前目录 (也就是**main.c 所在的目录**)

  2. 系统默认的头文件目录 (比如 **/usr/include, /usr/local/include**等)。

但是gcc根本不知道 ../shared_code/usr/include 这个神奇的目录存在,所以肯定会编译中断。

因此我们需要这样操作:

那么此时就会有人说:这也不对啊,我以前使用gcc时,不也用到了库吗,但是我也没有用-I来指

明我要查找的头文件啊?

解释:gcc认识/usr/include与usr/local/include,就会自动在这里面找头文件,但是gcc并不认识

我们自己的my_lib.h。

当我们生成了可执行程序,需要调用这个动态库时,操作逻辑与链接静态库如出一辙。我们需要向编译器(gcc)准确传达三个维度的寻址信息:

  • -I指引头文件的搜索目录。就像是告诉编译器:去这个指定的文件柜里,翻阅函数的接口说明书。

  • -L指引库文件的搜索目录。就像是告诉链接器:别光在系统默认仓库找,去我指定的这个额外仓库里找找高阶零件。

  • -l指定需要链接的具体库名 。它紧跟在 -L 之后,明确点出零件的代号(例如 -lmyc 会自动去寻找 libmyc.so)。

操作如图所示:

最后来到了最终时刻,执行这个可执行程序,如图所示:

我们发现可执行程序main无法运行,原因是加载时找不到所需的库文件。虽然我们已通过-l参数

gcc在../shared_code/lib目录下查找,但问题依然存在。于是我们使用ldd命令来验证库文件是否确

实未被找到,结果如图所示:

解释:虽然gcc能够识别libmyc.so,但系统本身并不认识该库文件。生成可执行程序后,程序运

行将由系统接管。系统会在预设的库文件路径中查找依赖库,而我们的../shared_code/olib目录并

不在这些系统默认搜索路径中,因此会导致库文件找不到的问题。

解决办法

1.系统找不到,那我们就在运行前,临时强行塞给它一个搜索路径,我们让系统在

LD_LIBRARY_PATH 环境变量中查找库,操作如图所示:

但是这个是临时指定搜索路径,关闭终端后就失效了。

2.干脆在编译的时候,就把运行期的寻找路径死死烙印在可执行文件的骨子里

操作:

cpp 复制代码
gcc -o main main.o -I ../shared_code/usr/include -L ../shared_code/lib -lmyc -Wl,-rpath=../shared_code/lib

如图所示:

也是成功了的。

3.系统只认 /usr/lib 或者 /lib 这种目录,那我们直接把自己的库扔进这里面就行了,但是这样

会污染系统目录(不太建议这样做)。

4.修改系统的动态加载器配置文件,把你的目录合法注册进去。

操作:首先获取库的绝对路径 (假设是 /home/kong/shared_code/lib;其次,在

/etc/ld.so.conf.d/ 目录,用sudo touch新建一个文件 (比如 myc.conf),用sudo vim打开

这个文件把绝对路径写进去;最后,刷新系统缓存,sudo ldconfig。

如图所示:

此时在执行,就成功了。

3.补充点

1.GCC/G++ 默认采用动态链接方式,如需使用静态库,需添加 -static 参数,且系统中必须存在对应的静态库文件。

我们先创建一个.c文件,代码如图所示:

cpp 复制代码
#include<stdio.h>

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

操作如图所示:

通过ldd后的libc.so,也就是c语言的动态库,我们可以看到code默认是动态链接的,此时code的大

小为8360字节,也验证了我们的猜想,如果是静态链接呢?首先我们需要先安装静态库,在

centos下的安装操作:

cpp 复制代码
sudo yum install glibc-static

操作如图所示:

通过code的大小与ldd的信息,可以确定这是个静态链接。

2.Linux 系统中,大多数库在安装时默认优先安装动态库版本。

直接操作,如图所示:

cpp 复制代码
# 统计动态库 (.so) 的数量
find /usr/lib /usr/lib64 -name "*.so*" 2>/dev/null | wc -l
cpp 复制代码
# 统计静态库 (.a) 的数量
find /usr/lib /usr/lib64 -name "*.a" 2>/dev/null | wc -l

注:目前这个操作不需要认识,直接复制粘贴

结果如图所示:

可以看到动态库的数量是大几千,而静态库的数量少得可怜!这就直接从系统生态的层面上证明了:Linux 官方和各大软件厂商,默认发放和安装的都是动态库。

4.总结
  1. 动态库制作:必须使用 -fPIC 生成位置无关代码,再用 -shared 打包成 .so 文件。
  2. 编译链接阶段:需要使用 -I(头文件路径)、-L(库文件路径)、-l(库名)三个参数准确指引 gcc。
  3. 运行时加载gcc 能找到库 ≠ 系统运行时能找到库 。系统动态加载器(ld.so)默认只在标准路径搜索,因此需要通过以下方式解决:
    • LD_LIBRARY_PATH:临时设置(测试推荐)
    • -Wl,-rpath:编译时嵌入路径(项目推荐)
    • ldconfig:永久注册到系统(公共库推荐)
  4. 动态链接 vs 静态链接:Linux 系统默认优先使用动态库,程序体积更小,内存共享效率更高,这也是现代软件的主流方式。

二.外部库

我们现在没接触过太多的库,唯⼀接触过的就是C、C++标准库,这⾥我们可以推荐⼀个好玩的图形 库:ncurses,安装操作:

cpp 复制代码
// Centos下
sudo yum install -y ncurses-devel

// ubuntu下
sudo apt install -y libncurses-dev

系统中其实有很多库,它们通常由⼀组互相关联的⽤来完成某项常⻅⼯作的函数构成。⽐如⽤来处理 屏幕显⽰情况的函数(ncurses库)(看看就行)

代码如图所示:

cpp 复制代码
#include <ncurses.h>
#include <unistd.h>
#include <string.h>

#define PROGRESS_BAR_WIDTH 30
#define BORDER_PADDING 2
#define WINDOW_WIDTH (PROGRESS_BAR_WIDTH + 2 * BORDER_PADDING + 2) // 加边框的宽度
#define WINDOW_HEIGHT 5
#define PROGRESS_INCREMENT 3
#define DELAY 300000  // 微秒(300毫秒)

int main() {
    // 1. 初始化 ncurses 环境
    initscr();
    start_color();
    cbreak();
    noecho();
    curs_set(FALSE); // 隐藏光标,让界面更干净

    // 2. 初始化颜色对
    init_pair(1, COLOR_GREEN, COLOR_BLACK); // 已完成部分
    init_pair(2, COLOR_RED, COLOR_BLACK);   // 剩余部分

    // 3. 计算屏幕居中坐标并创建窗口
    int max_y, max_x;
    getmaxyx(stdscr, max_y, max_x);
    int start_y = (max_y - WINDOW_HEIGHT) / 2;
    int start_x = (max_x - WINDOW_WIDTH) / 2;
    WINDOW *win = newwin(WINDOW_HEIGHT, WINDOW_WIDTH, start_y, start_x);

    int progress = 0;
    int max_progress = PROGRESS_BAR_WIDTH;

    // 4. 核心渲染循环
    while (progress <= max_progress) {
        werase(win);    // 清除上一帧内容
        box(win, 0, 0); // 必须在擦除后立刻重新绘制边框!

        // 防溢出保护,确保进度不超过最大值
        int completed = (progress > max_progress) ? max_progress : progress;
        
        int bar_x = BORDER_PADDING + 1; 
        int bar_y = 1; // 进度条放在第 2 行 (y=1)

        // 渲染已完成部分 (注意必须用 wattron)
        wattron(win, COLOR_PAIR(1));
        for (int i = 0; i < completed; i++) {
            mvwprintw(win, bar_y, bar_x + i, "#");
        }
        wattroff(win, COLOR_PAIR(1));

        // 渲染剩余部分
        wattron(win, A_BOLD | COLOR_PAIR(2));
        for (int i = completed; i < max_progress; i++) {
            mvwprintw(win, bar_y, bar_x + i, " ");
        }
        wattroff(win, A_BOLD | COLOR_PAIR(2));

        // 渲染百分比文本
        char percent_str[10];
        int percent_val = (completed * 100) / max_progress;
        snprintf(percent_str, sizeof(percent_str), "%d%%", percent_val);
        int percent_x = (WINDOW_WIDTH - strlen(percent_str)) / 2;
        
        // 打印在进度条下方,避开底部边框 (y=3)
        mvwprintw(win, 3, percent_x, percent_str);

        wrefresh(win); // 刷新窗口,将缓冲区推送到屏幕

        progress += PROGRESS_INCREMENT;
        usleep(DELAY);
    }

    // 跑满 100% 后停顿 1 秒,让你能看清结果再退出
    sleep(1);

    // 5. 优雅清理资源
    delwin(win);
    endwin();
    return 0;
}

我们此时用到了第三方动态库 ncurses,虽然 库中有这个**ncurses**库,但是gcc不认识它,就要用-l

和这个库指定连接**,也就是让**让 GCC 去系统的公共大仓库里,精准提取这一个特定的库文件。

cpp 复制代码
gcc main.c -o main -lncurses

结果如图所示:

相关推荐
openKylin1 小时前
紧急安全通告|Linux内核Dirty Frag漏洞(CVE-2026-43284、CVE-2026-43500)
linux·安全·web安全
小明同学011 小时前
计算机网络编程---系统调用到并发模型
linux·c++·计算机网络
LinuxGeek10241 小时前
CVE-2026-31431 - Linux Copy-Fail 漏洞利用 (Rust版本)和检测方案
linux·运维·服务器
xinhuanjieyi1 小时前
vscode插件,.sec / .inc / .sc 文件添加关键字高亮
java·服务器·vscode
learning-striving1 小时前
centos9安装docker测试成功教程
linux·运维·服务器·docker·容器
Data_Journal1 小时前
Puppeteer指纹识别指南:循序渐进,简单易学!
服务器·前端·人工智能·物联网·媒体
feng_you_ying_li1 小时前
linux之文件系统(3)
linux·运维·服务器
sbjdhjd1 小时前
Docker 网络工业级实战手册
linux·运维·经验分享·笔记·docker·云原生·云计算
桌面运维家1 小时前
服务器异常登录日志排查方法与安全防护实战
运维·服务器·安全