一. 库的基础认知:是什么?有哪些?
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)
# 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)
// 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);
// 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)
// my_string.h
#pragma once
int my_strlen(const char* s);
// 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:
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 静态库使用场景与命令
静态库使用需指定 “头文件路径、库文件路径、库名”,核心命令格式(上面的使用过程中也体现了):
gcc 源文件.c -I头文件路径 -L库文件路径 -l库名 [-static]
-I:指定头文件搜索路径(默认搜索 /usr/include 等系统目录);
-L:指定库文件搜索路径(默认搜索 /lib 等系统目录);
-l:指定库名(需去掉前缀lib和后缀.a,如libmyc.a → -l myc);
-static:强制链接静态库(优先使用静态库,无静态库则报错)。
场景 1:头文件 / 库文件与源文件同目录
# 编译(同目录下可省略-I)
gcc main.c -lmystdio -L . -static
场景 2:头文件 / 库文件在独立路径
# 假设库文件在 ./stdc/lib,头文件在 ./stdc/include
gcc main.c -I./stdc/include -L./stdc/lib -lmystdio -static
场景 3:安装到系统目录(全局可用)
# 拷贝头文件到系统目录
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:
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)
# 同目录编译
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 库
# CentOS
sudo yum install -y ncurses-devel
# Ubuntu
sudo apt install -y libncurses-dev
5.2. 编写测试代码(大家可以自己试试别的)
#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;
}
# 编译(-lncurses指定链接ncurses库)
gcc test.c -o test -lncurses
# 运行(终端中显示动态进度条)
./test
结尾:
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:动静态库是 Linux 开发中代码复用的核心手段,掌握其制作、使用与选型,能显著提升开发效率和项目质量。静态库适合独立部署场景,动态库适合资源共享场景,实际开发中需根据需求灵活选择。本文从基础概念到实战案例,覆盖了库开发的核心流程,后续可进一步学习库的版本管理、符号隐藏、动态加载(dlopen/dlsym)等高级特性
————————————————
版权声明:本文为CSDN博主「草莓熊Lotso」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2503_91389547/article/details/157869692