从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

MCP客户端与服务端初使用——让deepseek调用查询天气的mcp来查询天气

MCP客户端与服务端初使用——让deepseek调用查询天气的mcp来查询天气

本系列主要通过调用天气的mcp server查询天气这个例子来学习什么是mcp,以及怎么设计mcp。话不多说,我们开始吧。主要参考的是B站的老哥做的一个教程,我把链接放到这里,大家如果有什么不懂的也可以去看一下。 https://www.bilibili.com/video/BV1NLXCYTEbj?spm_id_from=333.788.videopod.episodes&vd_source=32148098d54c83926572ec0bab6a3b1d https://blog.ZEEKLOG.net/fufan_LLM/article/details/146377471 最终的效果:让deepseek-v3使用天气查询的工具来查询指定地方的天气情况 技术介绍 MCP,即Model Context Protocol(模型上下文协议),是由Claude的母公司Anthropic在2024年底推出的一项创新技术协议。在它刚问世时,并未引起太多关注,反响较为平淡。然而,随着今年智能体Agent领域的迅猛发展,MCP逐渐进入大众视野并受到广泛关注。今年2月,

By Ne0inhk
可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

可以在命令行通过大模型使用上下文协议(MCP)与外部工具交互的软件:小巧的MCPHost

小巧的MCPHost MCPHost 可以在命令行下使用,使大型语言模型(LLM)能够通过模型上下文协议(MCP)与外部工具进行交互。目前支持Claude 3.5 Sonnet和Ollama等。本次实践使用自己架设的Deepseek v3模型,跑通了Time MCP服务。  官网:GitHub - mark3labs/mcphost: A CLI host application that enables Large Language Models (LLMs) to interact with external tools through the Model Context Protocol (MCP). 下载安装 使用非常方便,直接下载解压即可使用。官网提供Windows、Linux和MacOS三个系统的压缩包: https://github.com/

By Ne0inhk
实战篇:Python开发monogod数据库mcp server看完你就会了

实战篇:Python开发monogod数据库mcp server看完你就会了

原创不易,请关注公众号:【爬虫与大模型开发】,大模型的应用开发之路,整理了大模型在现在的企业级应用的实操及大家需要注意的一些AI开发的知识点!持续输出爬虫与大模型的相关文章。 前言 目前mcp协议是给deepseek大模型插上工具链的翅膀,让大模型不仅拥有超高的推理和文本生成能力,还能具备执行大脑意识的工具能力! 如何开发一个mcp? mcp是一种协议,指的是模型上下文协议 (Model Context Protocol)。 官方结成的mcp https://github.com/modelcontextprotocol/python-sdk mcp库 pip install mcp from mcp.server.fastmcp import FastMCP 我们先来做一个简单的案例 from mcp.server.fastmcp import FastMCP import requests mcp = FastMCP("spider") @mcp.tool() def crawl(

By Ne0inhk
AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建

AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 作者:高瑞冬 本文目录 * AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建 * 一、MCP协议简介 * 二、创建MCP工具集 * 1. 获取MCP服务地址 * 2. 在FastGPT中创建MCP工具集 * 三、测试MCP工具 * 四、AI模型调用MCP工具 * 1. 调用单个工具 * 2. 调用整个工具集 * 五、私有化部署支持 * 1. 环境准备 * 2. 修改docker-compose.yml文件 * 3. 修改FastGPT配置 * 4. 重启服务 * 六、使用MCP-Proxy集成多个MCP服务 * 1. MCP-Proxy简介 * 2. 安装MCP-Proxy * 3. 配置MCP-Proxy * 4. 将MCP-Proxy与FastGPT集成 * 5. 高级配置

By Ne0inhk