从0到1搞懂Linux动静态库制作与底层原理|开发者必备指南

从0到1搞懂Linux动静态库制作与底层原理|开发者必备指南

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解

🌟心向往之行必能至


🎥Cx330🌸的简介:


目录

前言:

一、先搞懂:Linux下的库是什么?二进制的“代码积木”

1.1 库的本质

1.2 库的分类与系统位置

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

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

2.1 整体图示:理清思路

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

2.3 静态库使用场景与命令

2.4 静态库核心特点

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

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

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

3.3 动态库核心特点

四、直观对比:一张表看懂Linux动静态库所有区别

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

5.1 安装 ncurses 库

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

结尾:


前言:

在Linux软件开发中,“库”是程序员高效开发的核心工具——我们写的每一个printf、每一次字符串操作,本质上都是在调用C标准库函数。它把成熟、可复用的代码打包成二进制文件,让我们无需重复造轮子,直接站在巨人的肩膀上提升开发效率。但很多开发者只懂“用库”,却不懂“造库”,更不清楚其底层运行逻辑。今天,我们就彻底撕开动静态库的神秘面纱,聚焦Linux环境,从原理剖析到实战操作,手把手带你搞懂Linux动静态库的制作、使用与核心区别。

一、先搞懂:Linux下的库是什么?二进制的“代码积木”

1.1 库的本质

首先明确核心定义:Linux下的库(Library)是预先编写好的、可复用的代码集合,以二进制形式存在,能被Linux系统载入内存执行。它就像乐高积木,不同的积木块(库函数)可以组合出各种复杂的程序(模型),核心价值在于代码复用、模块化协作、提升开发效率,同时隐藏内部实现细节,只暴露统一接口供开发者调用。

从链接方式来看,Linux下的库主要分为两大类,这也是我们后续重点讲解的核心:

  • 静态库:Linux下后缀为.a。程序编译链接时,会把库的代码直接复制到可执行文件中,运行时完全不依赖原库文件,相当于“打包带走”的代码。
  • 动态库:Linux下后缀为.so。程序编译时仅记录函数入口地址,运行时才去加载库文件并调用函数,多个程序可共享同一份动态库,实现“一份代码,多处使用”。

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)

#pragma once typedef struct { int fd; int flags; int mode;// 刷新策略 char outbuffer[1024]; int cap; int size; //char inbuffer[1024]; }My_FILE; #define NONE_CACHE 1 #define LINE_CACHE 2 #define FULL_CACHE 4 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 Myclose(My_FILE *fp);

// my_stdio.c #include"my_stdio.h" #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> static mode_t gmode=0666; //My_FILE *fp=Myopen("log.txt","a"); 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,gmode); (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,gmode); (void)fd; } else{} if(fd<0) return NULL; // 创建My_FILE对象 My_FILE *fp=(My_FILE*)malloc(sizeof(My_FILE)); if(!fp) return NULL; fp->fd=fd; fp->flags=flags; fp->mode =LINE_CACHE; fp->cap=1024; fp->size=0; fp->outbuffer[0]='\0'; memset(fp->outbuffer,0,sizeof(fp->outbuffer)); return fp; } int Myfwrite(const char *message,int size,int num,My_FILE *fp) { if(message==NULL||fp==NULL) return -1; // 向文件里面写,本质是向缓冲区写 int total=size*num; if(total+fp->size > fp->cap-1) return -1; //写入 memcpy(fp->outbuffer+fp->size ,message,total); fp->size+=total; fp->outbuffer[fp->size]=0; if(fp->outbuffer[fp->size-1]=='\n' && (fp->mode & LINE_CACHE)) Myfflush(fp); } void Myfflush(My_FILE *fp) { if(!fp) return; //判断是否刷新 if(fp->size>0) { // 系统调用 // 从用户缓冲区拷贝到内核,WB, Write Back write(fp->fd,fp->outbuffer,fp->size); fp->size=0; // WT—Write Through // 不仅仅要写入到内核缓冲区,必须给我写到对应的硬件上 fsync(fp->fd); } } void Myclose(My_FILE *fp) { if(!fp) return; // 先刷新再关闭 Myfflush(fp); close(fp->fd); } 

(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:%.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 文件即可);多个程序共享库代码,节省资源;
  • 缺点:运行时依赖动态库,缺失会导致程序无法启动;启动时需加载库,速度略慢于静态库。

四、直观对比:一张表看懂Linux动静态库所有区别

对比维度

静态库(.a)

动态库(.so)

链接阶段

编译时完全嵌入可执行文件

运行时动态加载

内存占用

多个程序运行时多份副本,占用高

共享内存,占用低

可执行文件大小

较大(包含库代码)

较小(仅记录入口地址)

移植性

强(自包含,不依赖外部文件)

弱(运行时需依赖动态库文件)

更新维护

库更新需重新编译所有依赖程序

库更新无需重新编译程序,直接替换库文件即可

核心总结:Linux静态库适合追求移植性、不介意文件大小的场景(如嵌入式Linux开发);动态库适合多程序共享、注重系统资源占用的场景(如Linux桌面应用、服务器开发)。

相关问题:


五. 实战:使用外部库(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 

结尾:

静态库(.a)在编译时嵌入可执行文件,独立运行但体积大;动态库(.so)运行时加载,共享复用节省资源。两种库的制作流程(Makefile自动化)、使用命令和核心区别,并提供了ncurses图形库的实战案例。关键点包括:库的二进制本质、静态库的归档制作、动态库的位置无关码生成、运行时路径配置等,帮助开发者掌握Linux库的核心原理与实用技巧。

Read more

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk
【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

【AI大模型】DeepSeek + 通义万相高效制作AI视频实战详解

目录 一、前言 二、AI视频概述 2.1 什么是AI视频 2.2 AI视频核心特点 2.3 AI视频应用场景 三、通义万相介绍 3.1 通义万相概述 3.1.1 什么是通义万相 3.2 通义万相核心特点 3.3 通义万相技术特点 3.4 通义万相应用场景 四、DeepSeek + 通义万相制作AI视频流程 4.1 DeepSeek + 通义万相制作视频优势 4.1.1 DeepSeek 优势 4.1.2 通义万相视频生成优势 4.2

By Ne0inhk
【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

【DeepSeek微调实践】DeepSeek-R1大模型基于MS-Swift框架部署/推理/微调实践大全

系列篇章💥 No.文章01【DeepSeek应用实践】DeepSeek接入Word、WPS方法详解:无需代码,轻松实现智能办公助手功能02【DeepSeek应用实践】通义灵码 + DeepSeek:AI 编程助手的实战指南03【DeepSeek应用实践】Cline集成DeepSeek:开源AI编程助手,终端与Web开发的超强助力04【DeepSeek开发入门】DeepSeek API 开发初体验05【DeepSeek开发入门】DeepSeek API高级开发指南(推理与多轮对话机器人实践)06【DeepSeek开发入门】Function Calling 函数功能应用实战指南07【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:本地部署与API服务快速上手08【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:Web聊天机器人部署指南09【DeepSeek部署实战】DeepSeek-R1-Distill-Qwen-7B:基于vLLM 搭建高性能推理服务器10【DeepSeek部署实战】基于Ollama快速部署Dee

By Ne0inhk

DeepSeek各版本说明与优缺点分析_deepseek各版本区别

DeepSeek各版本说明与优缺点分析 DeepSeek是最近人工智能领域备受瞩目的一个语言模型系列,其在不同版本的发布过程中,逐步加强了对多种任务的处理能力。本文将详细介绍DeepSeek的各版本,从版本的发布时间、特点、优势以及不足之处,为广大AI技术爱好者和开发者提供一份参考指南。 1. DeepSeek-V1:起步与编码强劲 DeepSeek-V1是DeepSeek的起步版本,这里不过多赘述,主要分析它的优缺点。 发布时间: 2024年1月 特点: DeepSeek-V1是DeepSeek系列的首个版本,预训练于2TB的标记数据,主打自然语言处理和编码任务。它支持多种编程语言,具有强大的编码能力,适合程序开发人员和技术研究人员使用。 优势: * 强大编码能力:支持多种编程语言,能够理解和生成代码,适合开发者进行自动化代码生成与调试。 * 高上下文窗口:支持高达128K标记的上下文窗口,能够处理较为复杂的文本理解和生成任务。 缺点: * 多模态能力有限:该版本主要集中在文本处理上,缺少对图像、语音等多模态任务的支持。 * 推理能力较弱:尽管在自然语言

By Ne0inhk