Linux 动静态库完全指南:制作、使用、原理与实战

一. 库的基础认知:是什么?有哪些?

1.1 库的本质

库是编译后的二进制文件,包含可复用的代码和数据,本质是 "提前写好、经过验证的成熟代码"。其核心价值在于:

  • 避免重复开发:无需从零实现基础功能(如字符串处理、文件 IO);
  • 简化项目管理:将复杂功能拆分到库中,降低主项目复杂度;
  • 隐藏实现细节:只暴露接口,保护核心逻辑。

1.2 库的分类与系统位置

Linux 下库分为两类,命名和存储路径有明确规范:

类型 后缀 (Linux) 后缀 (Windows) 系统默认路径 核心特征
静态库 .a .lib /lib, /usr/lib, /usr/local/lib 编译时链接,可执行程序独立运行
动态库 .so .dll /lib64, /usr/lib64, /usr/local/lib64 运行时链接,多程序共享

系统库示例(Ubuntu/CentOS)

bash 复制代码
# Ubuntu查看C标准库
ls -l /lib/x86_64-linux-gnu/libc-2.31.so  # 动态库
ls -l /lib/x86_64-linux-gnu/libc.a       # 静态库

# Ubuntu查看C++标准库
ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so -l # 动态库
ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a 	# 静态库

# CentOS查看C标准库
ls /lib64/libc-2.17.so -l	# 动态库
ls /lib64/libc.a -l		   # 静态库

# CentOS查看C++标准库
ls -l /lib64/libstdc++.so.6             # 动态库(软链接到实际版本)
ls -l /usr/lib/gcc/x86_64-redhat-linux/4.8.2/libstdc++.a  # 静态库

1.3 预备工作:自定义库源码

后续动静态库制作将基于以下自定义源码(模拟文件 IO 和字符串工具库):
(1)文件 IO 库(my_stdio.h/my_stdio.c)

cs 复制代码
// my_stdio.h
#pragma once 
#define SIZE 1024
// 定义为 1,2,4方便使用 & 操作, 都只有一个比特位为 1
#define FLUSH_NONE 1
#define FLUSH_LINE 2
#define FLUSH_FULL 4

typedef struct 
{
    int fileno; // 文件描述符
    int flags;
    int fstrategy; // 刷新策略
    char outbuffer[SIZE];
    int cap; // 容量
    int size;
    //char inbuffer[1024];
}My_FILE;


My_FILE* Myfopen(const char* pathname, const char* mode); // r w a
int Myfwrite(const char* message, int size, int num, My_FILE* fp);
void Myfflush(My_FILE* fp);
void Myfclose(My_FILE* fp);
cs 复制代码
// my_stdio.c
#include "my_stdio.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

static mode_t fmode = 0666; // 文件权限

My_FILE* Myfopen(const char* pathname, const char* mode) // r w a
{
    if(pathname == NULL || mode == NULL)
        return NULL;
    umask(0);
    int fd = 0;
    int flags = 0;
    if(strcmp(mode, "w") == 0)
    {
        flags = O_CREAT | O_WRONLY | O_TRUNC;
        fd = open(pathname, flags, fmode);
        (void) fd;
    }
    if(strcmp(mode, "r") == 0)
    {
        flags = O_RDONLY;
        fd = open(pathname, flags);
        (void) fd;
    }
    if(strcmp(mode, "a") == 0)
    {
        flags = O_CREAT | O_WRONLY | O_APPEND;
        fd = open(pathname, flags, fmode);
        (void) fd;
    }
    else{}

    if(fd < 0) return NULL;
    // 创建 My_FILE对象
    My_FILE* fp = (My_FILE*)malloc(sizeof(My_FILE));
    if(!fp) return NULL;
    fp->fileno = fd;
    fp->flags = flags;
    fp->fstrategy = FLUSH_LINE;
    fp->outbuffer[0] = '\0';
   // memset(fp->outbuffer, 0, sizeof(fp->outbuffer));
   fp->cap = SIZE;
   fp->size = 0;
   return fp;
}


void Myfflush(My_FILE* fp)
{
    if(!fp) return;
    if(fp->size > 0)
    {
        // 写到内核文件的文件缓冲区中
        write(fp->fileno, fp->outbuffer, fp->size);
        // 刷新到外设
        fsync(fp->fileno);
        fp->size = 0;
    }
}

int Myfwrite(const char* message, int size, int num, My_FILE* fp)
{
    if(message == NULL || fp == NULL) return 0;

    // C语言向文件写入实际上是向缓冲区写入
    int sizeNum = size * num;
    if(fp->size + sizeNum < fp->cap - 1) // 预留\0的位置
    {
        memcpy(fp->outbuffer + fp->size, message, sizeNum);
        fp->size += sizeNum;
        fp->outbuffer[fp->size] = 0;
    }

    // 刷新缓冲区条件: 不是每次都刷新的,这样也可以加快响应速度
    if(fp->size > 0 && fp->outbuffer[fp->size - 1] == '\n' && (fp->fstrategy & FLUSH_LINE))
    {
        Myfflush(fp);
    }

    return sizeNum;
}


void Myfclose(My_FILE* fp)
{
    if(fp->size > 0)
    {
        Myfflush(fp);
    }
    close(fp->fileno);
}

(2)字符串库(my_string.h/my_string.c)

cs 复制代码
// my_string.h
#pragma once
int my_strlen(const char* s);
cs 复制代码
// my_string.c
#include "my_string.h"
int my_strlen(const char* s) 
{
    const char* end = s;
    while (*end != '\0') end++;
    return end - s;
}

总结与引入

二. 静态库:编译时链接,独立运行

静态库(.a)的核心特征是 "编译链接时,将库代码完整拷贝到可执行程序中",生成的可执行程序不依赖外部库,可独立运行。

2.1 整体图示:理清思路

  • 我们可以先看看这个图示的流程再来往下详细学习

2.2 静态库制作流程(Makefile 自动化,更简便)

静态库通过ar(GNU 归档工具)制作,核心步骤:编译源码生成.o 文件 → 归档.o 文件为.a 静态库。

  • 编写 Makefile:
bash 复制代码
target=libmyc.a
src=$(wildcard *.c)
obj=$(src:.c=.o)
cc=gcc -c
ar=ar -rc

$(target):$(obj)
	$(ar) $@ $^
# 编译.o文件(只编译不链接)
%.o:%.c
	$(cc) $<

# 输出库文件(模拟安装到系统目录结构)
.PHONY:output
output:
	@mkdir -p myc/lib 
	@mkdir -p myc/include
	@cp *.h myc/include
	@cp *.a myc/lib 
	@tar czf myc.tgz myc 

.PHONY:clean
clean:
	rm -rf *.o $(target) myc myc.tgz 

debug:
	@echo $(target)
	@echo $(src)
	@echo $(obj)
  • 后续操作如下图所示:

2.3 静态库使用场景与命令

静态库使用需指定 "头文件路径、库文件路径、库名",核心命令格式(上面的使用过程中也体现了):

bash 复制代码
gcc 源文件.c -I头文件路径 -L库文件路径 -l库名 [-static]
  • -I:指定头文件搜索路径(默认搜索 /usr/include 等系统目录);
  • -L:指定库文件搜索路径(默认搜索 /lib 等系统目录);
  • -l:指定库名(需去掉前缀lib和后缀.a,如libmyc.a → -l myc);
  • -static:强制链接静态库(优先使用静态库,无静态库则报错)。

场景 1:头文件 / 库文件与源文件同目录

bash 复制代码
# 编译(同目录下可省略-I)
gcc main.c -lmystdio -L . -static

场景 2:头文件 / 库文件在独立路径

bash 复制代码
# 假设库文件在 ./stdc/lib,头文件在 ./stdc/include
gcc main.c -I./stdc/include -L./stdc/lib -lmystdio -static

场景 3:安装到系统目录(全局可用)

bash 复制代码
# 拷贝头文件到系统目录
sudo cp *.h /usr/include/
# 拷贝静态库到系统目录
sudo cp libmystdio.a /usr/lib/
# 直接编译(无需指定-I和-L,但是 -l 一定还是必须的)
gcc main.c -lmystdio -static

2.4 静态库核心特点

  • 优点:可执行程序独立运行,不依赖外部库;运行时无需加载库,启动速度快;
  • 缺点:可执行程序体积大(包含库代码);库更新后需重新编译链接;多个程序使用会重复占用磁盘和内存。

三. 动态库:运行时链接,共享复用

动态库(.so)的核心特征是 "编译时仅记录库依赖,运行时才加载库代码",多个程序可共享同一库文件,节省磁盘和内存空间。

3.1 动态库制作流程(Makefile 自动化)

动态库制作需生成 "位置无关码(PIC)",核心步骤:编译 PIC 目标文件 → 链接为共享库。

  • 编写 Makefile:
bash 复制代码
target=libmyc.so
src=$(wildcard *.c)
obj=$(src:.c=.o)
cc=gcc 

$(target):$(obj)
	$(cc) -shared -o $@ $^
# 编译PIC目标文件(位置无关码,支持任意地址加载)
%.o:%.c
	$(cc) -fPIC -c $<

.PHONY:output
output:
	@mkdir -p myc/lib 
	@mkdir -p myc/include
	@cp *.h myc/include
	@cp *.so myc/lib 
	@tar czf myc.tgz myc 

.PHONY:clean
clean:
	rm -rf *.o $(target) myc myc.tgz 

debug:
	@echo $(target)
	@echo $(src)
	@echo $(obj)

后续操作如下图所示:

3.2 动态库使用:编译与运行时依赖

动态库编译命令与静态库类似,但运行时需确保系统能找到动态库(否则报错 "libmystdio.so not found")。

  • 步骤 1:编译(同静态库命令,无需 - static)
bash 复制代码
# 同目录编译
gcc main.c -L. -lmystdio
# 独立路径编译
gcc main.c -I./stdc/include -L./stdc/lib -lmystdio

步骤 2:解决运行时库搜索路径

动态库运行时搜索路径优先级:

  • 编译时指定的-rpath(嵌入可执行程序);
  • 环境变量LD_LIBRARY_PATH
  • 系统配置文件/etc/ld.so.conf.d/(需执行ldconfig生效);
  • 系统默认路径(/lib64、/usr/lib64)。

解决方案(任选其一,看图示吧)

3.3 动态库核心特点

  • 优点:可执行程序体积小;库更新后无需重新编译(替换.so 文件即可);多个程序共享库代码,节省资源;
  • 缺点:运行时依赖动态库,缺失会导致程序无法启动;启动时需加载库,速度略慢于静态库。

四. 动静态库对比与选型建议

对比维度 静态库 (.a) 动态库 (.so)
链接时机 编译、链接阶段 程序运行阶段
可执行程序体积 大(库代码被复制进去) 小(仅记录依赖信息)
运行依赖 无需外部文件,独立运行 必须依赖对应的.so文件
库更新 需重新编译、链接整个程序 直接替换.so文件即可生效
内存占用 多个程序重复占用内存 多个程序共享内存中的同一份代码
编译速度 快(链接后无需额外处理) 略慢(需处理动态链接信息)
适用场景 小程序、嵌入式(追求无依赖) 大型项目、多程序共享(节省资源)

选型建议:

  • 若程序需独立部署(如嵌入式设备),选静态库;
  • 若多个程序共用同一功能(如公司内部工具库),选动态库;
  • 若库更新频繁(如业务逻辑迭代快),选动态库;
  • 若追求启动速度和稳定性,选静态库。

相关问题:

五. 实战:使用外部库(ncurses 图形库)

除了自定义库,Linux 系统提供大量现成外部库,以 ncurses(终端图形库)为例,演示外部库的安装与使用。

5.1 安装 ncurses 库

bash 复制代码
# CentOS
sudo yum install -y ncurses-devel
# Ubuntu
sudo apt install -y libncurses-dev

5.2. 编写测试代码(大家可以自己试试别的)

bash 复制代码
#include <ncurses.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define MAX_DATA 30
#define HEIGHT 20
#define WIDTH 60

int main() {
    int data[MAX_DATA] = {0};
    int index = 0;
    
    // 初始化ncurses
    initscr();
    cbreak();
    noecho();
    curs_set(0);
    nodelay(stdscr, TRUE);  // 非阻塞输入
    keypad(stdscr, TRUE);
    
    // 初始化颜色(如果终端支持)
    if (has_colors()) {
        start_color();
        init_pair(1, COLOR_GREEN, COLOR_BLACK);
        init_pair(2, COLOR_YELLOW, COLOR_BLACK);
        init_pair(3, COLOR_RED, COLOR_BLACK);
        init_pair(4, COLOR_CYAN, COLOR_BLACK);
    }
    
    // 生成初始随机数据
    srand(time(NULL));
    for (int i = 0; i < MAX_DATA; i++) {
        data[i] = rand() % (HEIGHT - 2) + 1;
    }
    
    // 主循环
    int ch;
    while ((ch = getch()) != 'q') {
        clear();
        
        // 绘制边框和标题
        if (has_colors()) attron(COLOR_PAIR(4));
        box(stdscr, 0, 0);
        mvprintw(0, 2, " CPU Usage Monitor (Press 'q' to quit) ");
        if (has_colors()) attroff(COLOR_PAIR(4));
        
        // 绘制坐标轴
        mvaddch(HEIGHT, 1, ACS_LTEE);
        for (int i = 0; i < WIDTH - 2; i++) {
            mvaddch(HEIGHT, i + 2, ACS_HLINE);
        }
        mvaddch(HEIGHT, WIDTH - 1, ACS_RTEE);
        
        // 绘制Y轴刻度
        mvprintw(1, 1, "100%%");
        mvprintw(HEIGHT/2, 1, " 50%%");
        mvprintw(HEIGHT-1, 1, "  0%%");
        
        // 更新数据(模拟实时变化)
        data[index] = rand() % (HEIGHT - 2) + 1;
        index = (index + 1) % MAX_DATA;
        
        // 绘制数据点并连线
        for (int i = 0; i < MAX_DATA; i++) {
            int x = (i * (WIDTH - 4)) / MAX_DATA + 5;
            int y = HEIGHT - data[(index + i) % MAX_DATA];
            
            // 根据数值选择颜色
            if (has_colors()) {
                if (data[(index + i) % MAX_DATA] > (HEIGHT * 2) / 3)
                    attron(COLOR_PAIR(3));
                else if (data[(index + i) % MAX_DATA] > HEIGHT / 3)
                    attron(COLOR_PAIR(2));
                else
                    attron(COLOR_PAIR(1));
            }
            
            // 绘制点
            mvaddch(y, x, '*');
            
            // 绘制连线(如果下一个点存在)
            if (i < MAX_DATA - 1) {
                int next_x = ((i + 1) * (WIDTH - 4)) / MAX_DATA + 5;
                int next_y = HEIGHT - data[(index + i + 1) % MAX_DATA];
                
                // 简单的连线算法
                int dx = next_x - x;
                int dy = next_y - y;
                int steps = abs(dx) > abs(dy) ? abs(dx) : abs(dy);
                
                for (int s = 1; s < steps; s++) {
                    int inter_x = x + (dx * s) / steps;
                    int inter_y = y + (dy * s) / steps;
                    if (inter_y >= 1 && inter_y < HEIGHT) {
                        mvaddch(inter_y, inter_x, '.');
                    }
                }
            }
            
            if (has_colors()) attroff(COLOR_PAIR(1) | COLOR_PAIR(2) | COLOR_PAIR(3));
        }
        
        // 显示当前数值
        mvprintw(HEIGHT + 1, 2, "Current: %3d%%   Average: %3d%%", 
                 (data[index] * 100) / HEIGHT,
                 ((HEIGHT - (HEIGHT - data[index]) / 2) * 100) / HEIGHT);
        
        refresh();
        usleep(300000);  // 每0.3秒更新一次
    }
    
    endwin();
    return 0;
}
bash 复制代码
# 编译(-lncurses指定链接ncurses库)
gcc test.c -o test -lncurses
# 运行(终端中显示动态进度条)
./test
相关推荐
孙同学_2 小时前
【Linux篇】Socket编程TCP
linux·网络·tcp/ip
沃和莱特2 小时前
Copy as fetch + Skill:自动化问题记录分析的实践与思考
运维·ai·自动化·编程·skills
赛博云推-Twitter热门霸屏工具2 小时前
从手动运营到自动化增长:赛博云推让Twitter推广效率提升10倍
运维·自动化·twitter
Crazy CodeCrafter2 小时前
租金要交,但客流为零,要关店了?
大数据·运维·经验分享·自动化·开源软件
zopple2 小时前
Knife4j文档请求异常(基于SpringBoot3,查找原因并解决)
java·服务器·数据库
侯侯Hou2 小时前
Linux系统安装OpenClaw
linux
charlie1145141912 小时前
2026年IMX6ULL正点原子Alpha开发板学习方案——U-Boot完全移植概览:从官方源码到你的自制板,这条路有多远
linux·学习·嵌入式·uboot·嵌入式linux·工程实践·编程指南
oioihoii3 小时前
数据库查询优化中的谓词下推策略与成本感知优化实践
服务器·数据库·oracle
学习是生活的调味剂3 小时前
大模型应用之使用LangChain实现RAG(二)智能客服
服务器·数据库·langchain