虚拟机网络模式

配置 Ubuntu 联网时,稳定的网络连接是基础前提!

虚拟机里的这些网络模式(桥接、NAT、仅主机、自定义、LAN段),决定了 Ubuntu 虚拟机如何跟主机、外部网络打通;选对模式,既能让 Ubuntu 联网装软件,又能让主机顺利实现远程;

模式 核心特点 外网访问能力 与主机通信能力 典型应用场景
桥接模式 虚拟机与主机共享物理网卡,获取同网段 IP 可直接访问外网 同一网段可双向通信 需对外提供服务时用,如搭建 Web 服务器、与其他设备互相访问等
NAT 模式 虚拟机用私有 IP,流量经主机 NAT 转发 可访问外网(依赖主机 NAT 转发) 天然互通(无需额外配置) 开发首选,如快速联网装环境、主机直连 SSH 等
仅主机模式 虚拟机连封闭虚拟网卡 与外网完全隔离,无法访问外网 同一虚拟网络可双向通信 本地隔离测试,如单机服务调试、数据不对外的开发环境等
自定义模式 指定 VMnet1/8/0,灵活配网络 取决于所选虚拟网络 取决于所选虚拟网络 复杂场景,如多虚拟网卡协同、特定协议模拟等
LAN 区段 多虚拟机连专属虚拟网卡,区段外无法访问 默认无法访问外网 默认无法通信 模拟复杂网络,如多虚拟机模拟服务器集群,测试内部通信等

日常开发(尤其是个人/常规项目开发)中,NAT 模式和桥接模式因适配开发需求、配置简单,使用频率远高于其他模式;仅主机、自定义、LAN 区段模式受场景限制,很少在常规开发流程中用到;

对比维度 NAT 模式 桥接模式
网络原理 虚拟机借主机网络“转发”上网(类似连热点) 虚拟机与主机平级连物理网络(类似插网线)
IP配置 自动分配,无需手动 需手动设同网段 IP(易冲突)
联网复杂度 选模式即联网,零配置 需配 IP / 网关 / DNS,易出错
VS Code远程连接 天然互通,填 IP 直连 需确保 IP 同网段且不冲突
开发适配场景 首选!日常写代码、装环境、远程开发 需对外提供服务(如虚拟机搭网站供手机访问)
安全隔离性 虚拟机藏于主机内网,防外部干扰 暴露在物理网络,易被局域网设备访问

NAT网段计算

在 VMware 虚拟机环境中,NAT 模式的网段由宿主机虚拟网卡(VMnet8)的网络配置决定;

NAT网段计算:

• 定位虚拟网卡:查看 Windows 主机中 VMware Network Adapter VMnet8 的网络属性;

• 获取关键参数:获取 IP 地址及子网掩码;

• 计算网段地址:网段 = 虚拟网卡IP 按位与(&) 子网掩码

以 192.168.159.1/24 网段为例(子网掩码 255.255.255.0),整个网段的 IP 范围为 192.168.159.0 ~192.168.159.255 ,但需严格区分可用主机地址与特殊地址:

• 可用主机地址:192.168.159.3 ~ 192.168.159.254(共252个可用地址),这些地址未被系统占用,可安全分配给虚拟机作为静态 IP;

• 特殊地址:特殊地址不可进行分配;

地址 用途说明 能否给虚拟机(Ubuntu)用
192.168.159.0 网段标识地址 不能(纯标识网段用)
192.168.159.1 宿主机虚拟网卡地址(VMnet8) 不能(已被主机占用,冲突必断网)
192.168.159.2 NAT 网关地址(转发流量用) 不能(已被系统占用,不能分配)
192.168.159.255 广播地址(给全网段发消息) 不能(用了会导致网段内设备混乱)

配置 Ubuntu NAT 联网流程

确认Windows环境 NAT 网段:在Windows操作系统中,借助VMware虚拟网络适配器(VMnet8)相关设置,查询并记录 NAT 模式下的网段信息(含 IP 地址、子网掩码等),为后续Ubuntu配置提供网络参数依据;

Ubuntu系统网络配置:

• 选定网络模式:将 Ubuntu 网络连接模式指定为 NAT 模式,使虚拟机可借助主机网络转发机制访问外部网络;

• 配置静态IP地址:需确保配置的静态IP地址处于 Windows 端得到的 NAT 网段可用范围内,保证网络通信正常;

• 验证静态 IP 配置:通过查看网络信息指令(ip addr),检查静态 IP 地址是否生效、网络参数是否正确等等,确保 Ubuntu 可正常上网;

配置Ubuntu SSH免密登录流程

更新软件源:

同步系统软件源信息,确保后续安装软件版本最新;

sudo apt update

SSH服务部署:

• 安装 OpenSSH 服务:为远程连接提供基础支持;

sudo apt install openssh-server

• 启动 SSH 服务:启动服务,使 Ubuntu 具备接收 SSH 连接的能力;

sudo systemctl start ssh

• 设置开机自启动:配置服务开机自动启动,避免系统重启后需手动启动;

sudo systemctl enable ssh

• 验证服务状态:检查服务运行状态,确保服务正确启动(状态显示active(running)为正常);

sudo systemctl status ssh

生成密钥对:

分别在 Windows 和 Ubuntu 系统生成 SSH 密钥对(用于加密认证);

• Windows 端:打开命令提示符,执行以下命令生成密钥对;

ssh-keygen

• Ubuntu 端:打开终端,执行以下命令生成密钥对;

ssh-keygen

公钥互信配置:

• 新建授权文件( Ubuntu 端):用于存储公钥,文件名固定;

touch ~/.ssh/authorized_keys

• 追加 Windows 公钥:Windows 系统生成的公钥文件,粘贴到 Ubuntu 系统的新建授权文件中;

• 追加 Ubuntu 公钥:将自身公钥也追加到授权文件,实现双向免密(可选,按需配置);

网络连通性验证与远程登录:

• ping 测试:在 Windows 命令提示符中,执行以下命令测试与 Ubuntu 系统的网络连通性;

ping Ubuntu的IP地址

• SSH 远程登录:若网络连通,执行以下命令通过 SSH 登录 Ubuntu 系统;

首次登录需输入 yes 确认,完成认证后即可进入 Ubuntu 主目录,实现免密远程访问;

ssh Ubuntu用户名@Ubuntu的IP地址

linux操作系统分层结构

IO介绍

IO概述:IO(Input/Output)是计算机系统中数据在不同实体间传输交互的抽象过程,涵盖程序与外部设备(如文件、硬件、网络)的数据读写行为。本质上,它是程序与外部环境建立“数据通道”的核心机制,支撑着程序获取输入、输出结果的基本逻辑,是软件与硬件、本地与远程交互的底层依托;

IO作用:IO 是程序与外部环境交互的基础桥梁。一方面,它为程序提供 “输入” 能力,支撑程序从文件、硬件、网络获取运行所需的数据(如游戏加载资源文件、程序读取用户输入);另一方面,通过 “输出” 功能,程序可将计算结果持久化存储(如写入日志文件)、可视化展示(如输出到终端)或远程传输(如网络数据发送);

IO种类:可从实现层级和应用场景两个维度分类

• 按实现层级分类:

• 系统调用IO:也称之为文件IO,直接对接操作系统内核的最底层接口;逻辑极简、性能接近硬件,但是需要手动处理缓冲区、错误码等细节;

• 库函数IO:也称之为标准IO,基于系统调用封装的高层接口;自动管理缓冲区、跨平台兼容、简化开发,是用户态程序的常用选择;

按应用场景分类:

• 文件 IO:针对磁盘文件的读写(如日志记录、配置文件操作).可通过“系统调用”或“库函数”实现;

• 设备 IO:与硬件设备交互(如键盘输入、显示器输出).操作系统通过“设备抽象”将设备转换为文件系统中的特殊文件,使程序能用统一的文件操作接口访问所有硬件,这正是 Linux“一切皆文件”理念的核心;

• 网络 IO:实现网络数据收发(如浏览器访问网页、APP通讯).依赖系统调用或者是更高层的框架;

对比维度 系统调用 库函数
层级定位 操作系统内核提供的底层接口(如 open 基于系统调用 / 纯用户逻辑封装的高层工具(如 C 标准库 fopen
运行空间 运行于内核空间,需切换 CPU 特权级(用户态 → 内核态) 运行于用户空间,无需特权级切换
功能本质 直接操作硬件 / 内核资源(如读写磁盘、创建进程) ① 封装系统调用实现复杂功能(如 printf 调用 write
② 纯用户态逻辑(如 strlen 计算字符串长度)
性能开销 因“用户态 ↔ 内核态”切换,开销较大 ① 若封装系统调用,开销略低(减少切换次数)
② 纯用户态函数开销极小
移植性 依赖操作系统内核,移植性差(如 Linux 与 Windows 系统调用不同) 跨平台库(如 C 标准库),移植性好(同一函数适配多系统)

标准IO介绍

定义:标准 I/O 是 C 库提供的输入输出函数集合,由 ANSI C 标准统一规范,为跨平台 IO 操作奠定基础;

跨平台性:在UNIX 系统、Windows系统、Linux 等多系统操作均实现标准 I/O 库,让代码跨平台开发更顺畅;

高效实现原理:通过缓冲机制(内存临时存储数据),减少直接系统调用次数,降低硬件交互开销,提升 IO效率;

易用性设计:基于系统底层调用封装,屏蔽复杂硬件操作,开发者无需关注设备细节,简单调用函数即可完成 IO;

流(stream)

定义:所有 I/O 操作抽象为字节的“移入/移出”程序的过程,这种字节流动形态,称为流

分类:

文件流:按字符编码(如 ASCII、UTF-8)处理,注重文本语义;

二进制流:直接操作原始字节,保留数据原始形态(如图片、可执行文件);

默认打开的流:

• stdin:标准输入流(如键盘输入)

• stdout:标准输出流(如屏幕打印)

• stderr:标准错误流(专用于输出错误信息)

FILE对象

定义:FILE 对象是一个结构体,用于存储文件或流相关的所有状态信息(如文件描述符、缓冲区大小、缓冲区当前位置、文件结束标志等等);

FILE对象与流对应关系:

• 每个流在内存中由一个 FILE 对象表示,通过 FILE* 指针访问;

标准流的特殊 FILE 对象:

• stdin:是标准输入流对应的 FILE 对象指针,通常关联到键盘输入;

• stdout:是标准输出流对应的 FILE 对象指针,通常关联到控制台输出;

• stderr:是标准错误流对应的 FILE 对象指针。通常关联到控制台,用于输出错误信息;

由于 FILE 对象通常较大且内部结构复杂,标准 I/O 函数不直接操作FILE对象本身,而是通过FILE 指针(FILE*)来间接访问。FILE 指针是指向 FILE 对象的内存地址,所有文件操作(如打开、读写、关闭)都基于这个指针进行;

FILE指针创建与销毁:通过fopen( )进行创建FILE指针,通过fclose( )进行销毁FILE指针;

FILE指针操作:所有标准 I/O 函数都以 FILE 指针作为参数,常见操作包括读写操作、定位操作、错误检查等等;

FILE 对象与底层系统调用的关系:FILE 对象是标准库层面的抽象,而 Linux 内核通过文件描述符(如 0、1、2 对应标准输入 / 输出 / 错误)管理文件。FILE 对象内部维护了一个文件描述符,将标准 I/O 函数映射到底层系统调用(如 read()、write()),并提供缓冲区优化;

系统头文件tags生成及vim验证

为系统头文件(/usr/include)生成tags索引,让vim可快速跳转、检索头文件中的函数、类型、宏等;

man手册使用方式

man手册是用于在linux系统中查询命令、函数等的帮助手册,不同的章节分类便于快速定位获取各类linux系统相关的帮助信息;

以下是一些常用用法:

• man man:查看man手册自身的章节说明,了解man手册整体架构和使用规范;

• man 1 要查看的内容:查看可执行程序或shell命令的帮助文档,“1”对应用户命令章节;

• man 2 要查看的内容:查看系统调用(内核提供的函数)相关帮助,“2”对应系统调用章节;

• man 3 要查看的内容:查看库调用(库中的函数),“3”对应库函数章节;

......

fopen函数

项目 详细说明
所需头文件 #include <stdio.h>
原型 FILE *fopen(const char *pathname, const char *mode);
功能 用标准 I/O 的方式打开文件
参数 pathname 文件路径和名字。不写路径默认打开当前路径下的文件。
mode 打开文件的方式
r:只读,光标定位到文件开头,文件不存在则报错。
r+:读写,光标定位到文件开头,文件不存在则报错。
w:只写,文件存在则清空,不存在则创建,光标定位到开头。
w+:读写,文件存在则清空,不存在则创建,光标定位到开头。
a:追加(只写),文件不存在则创建,存在则不清空,光标定位到结尾。
a+:读和追加,文件不存在则创建,存在则不清空;读操作时光标在文件开头,写操作总是在文件结尾。
返回值 成功:返回 FILE 指针
失败:返回 NULL,并设置错误码

fclose函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fclose(FILE *stream);
功能 用标准IO的方式关闭文件
参数 stream 文件指针
返回值 成功:返回 0
失败:返回 EOF-1),并重置错误码

错误码的原理

在Linux系统中,内核通过预定义宏(如 ENOENT)标识错误,错误码为正整数,内核返回时用负数传递;

strerror函数-错误码转换为错误信息

项目 内容
所需头文件 #include <string.h>
原型 char *strerror(int errnum);
功能 将错误码转换为错误信息
参数 errnum 错误码
返回值 成功:返回指向错误信息的字符串指针
若 errnum 无效:返回结果由系统实现决定(通常返回通用错误描述,如 "Unknown error nnn"

perror函数-错误信息打印函数

项目 内容
所需头文件 #include <stdio.h>
原型 void perror(const char *s);
功能 打印错误信息。如果 s 不为 NULL 且 *s 不为空字节('\0'),则先打印参数字符串 s,后跟一个冒号和一个空格;然后打印与当前 errno 值对应的错误消息和一个换行符。
参数 s 用户的附加信息。
• 若 s 非空(s != NULL 且 *s != '\0'),则输出 s:(字符串 + 冒号 + 空格)。
• 若 s 为空(s == NULL 或 *s == '\0'),则不同系统可能输出“: 错误信息”或直接输出“错误信息”。
返回值 无(void

fgetc函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fgetc(FILE *stream);
功能 从文件流中读取当前位置的字符。每次调用 fgetc 会自动移动文件指针,指向下一个字符位置,为后续读取做准备。
参数 stream 文件指针
返回值

成功:返回无符号字符(unsigned char)的数值(范围 0~255),并转换为 int 返回。

若返回 char,255(unsigned char)会被视为 -1(signed char),与 EOF 冲突。

失败或读到文件结束:返回 EOF(-1)。

fputc函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fputc(int c, FILE *stream);
功能 向文件中写入一个字符
参数 c 要写入的字符
stream 文件指针
返回值 成功:返回写入的字符的ASCII码
失败:返回EOF

fgets函数

项目 内容
所需头文件 #include <stdio.h>
原型 char *fgets(char *s, int size, FILE *stream);
功能

从 stream 指向的文件中读取字符串,存入 s 指向的缓冲区;

最多读取 size-1 个字符(包含可能的换行符 \n),自动在末尾添加 \0

遇到 EOF 或 \n 时停止,若因 \n 停止,则 \n 也会被存入 s

参数 s 存放读取内容的内存首地址
size 想要读取的字节数
stream 文件指针
返回值 - 成功:返回 s(即读取到的字符串地址)
- 失败或读到文件结束:返回 NULL

fputs函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fputs(const char *s, FILE *stream);
功能 将字符串 s 的内容写入到 stream 指向的文件中,仅写入字符串有效字符(不包含末尾 \0
参数 s 要写入字符串的首地址
stream 文件指针
返回值 成功:返回非负整数
失败:返回 EOF(-1)

缓冲区

数据流动流程图

标准IO的缓冲区

定义:标准 I/O 中的缓冲区是C语言标准库为优化文件读写效率设计的内存机制,本质上就是一段由FILE结构体管理的内存空间;

作用:通过临时存储数据、批量处理,可以减少硬件IO(如磁盘、终端)的交互次数,提升程序效率;

缓冲策略:标准 I/O 中设计了行缓冲、全缓冲、无缓冲三种策略,来适配不同的场景,也支持手动修改;

• 行缓冲:默认用于终端设备(如stdout、stdin),Linux系统中行缓冲的大小为1024(1KB);

• 全缓冲:默认用于文件(如 .txt),Linux系统中全缓冲大小通常是4096(4KB);

• 无缓冲:不使用临时内存暂存,数据直接写入硬件(如stderr);无缓冲区的大小为0;

缓冲模式 说明 典型对象
全缓冲 填满缓冲区才进行实际I/O(如写磁盘) 普通文件(fopen 打开的文件)
行缓冲 遇到换行符 \n 或缓冲区满时才I/O 终端交互设备(如 stdinstdout 指向终端时)
无缓冲 立即进行I/O,不缓存 stderr
行缓冲刷新时机:

• 遇到换行符“\n”会刷新缓冲区;

• 程序正常结束会刷新缓冲区;

• 当文件关闭时会刷新缓冲区;

• 在输入和输出发生切换时会刷新缓冲区;

• 缓冲区写满(未到换行符)会刷新缓冲区;

• 主动调用fflush函数会强制刷新缓冲区;

fflush函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fflush(FILE *stream);
功能 将指定流(stream)缓冲区中尚未写入的内容,强制写入到对应的输出设备(如文件、终端等)中
参数 stream 用于指定需要刷新缓冲区的流
返回值 成功:返回0,刷新了流缓冲区;
失败:返回EOF,并将errno设置为具体的错误类型
全缓冲刷新时机

• 程序正常结束会刷新缓冲区;

• 当文件关闭时会刷新缓冲区;

• 在输入和输出发生切换时会刷新缓冲区;

• 缓冲区写满(未到换行符)会刷新缓冲区;

• 主动调用fflush函数会强制刷新缓冲区;

手动设置缓冲

覆盖默认缓冲策略:不同系统/语言对IO流的默认缓冲设置不同(例如C语言中stdout默认行缓冲,stderr默认无缓冲),手动设置可按需调整;

性能与实时性平衡:

• 行缓冲:适合交互式输出(如命令行程序),每行输出后自动刷新;

• 全缓冲:适合大量数据批量写入(如日志文件),减少IO次数;

• 无缓冲:适合需要立即显示的关键信息(如错误日志);

特殊场景需求:例如网络编程中避免数据堆积,或调试时强制输出中间结果;

手动设置缓冲典型应用场景:

• 日志系统:根据日志级别设置缓冲(错误日志无缓冲,普通日志全缓冲);

• 交互式工具:命令行程序中确保用户输入后立即响应;

• 嵌入式系统:受限内存环境下自定义缓冲区大小;

setbuf函数

项目 内容
所需头文件 #include <stdio.h>
原型 void setbuf(FILE *stream, char *buf);
功能 设置指定文件流的缓冲区
参数 strea 指向 FILE 对象的指针,标识要设置缓冲区的文件流
buf 指向用户提供的缓冲区的指针。如果提供了缓冲区,其大小必须至少为 BUFSIZ 字节(通常在 stdio.h 中定义);如果为 NULL,则是无缓冲模式
返回值

setbuf函数允许两种方式的缓冲:

• 全缓冲:如果buf指向一个大小大于0的缓冲区,那么IO操作将使用全缓冲模式;

• 无缓冲:如果buf为NULL,或者是大小为0,那么IO操作将变为无缓冲模式;

setbuf函数注意事项:

• 调用setbuf函数应该在打开文件流或者是任何IO操作之前,否则可能不会生效;

• 如果要使用自定义的缓冲区,确保缓冲区足够大,以避免缓冲区溢出;

• 当使用自定义缓冲区时,不要在buf指向的缓冲区被释放后再进行IO操作,以免导致未定义的行为;

sleep函数

项目 内容
所需头文件 #include <unistd.h>
原型 unsigned int sleep(unsigned int seconds);
功能 用于让程序暂停执行指定的时间
参数 seconds 指定程序暂停执行的秒数
返回值 成功:返回0;
失败:若程序被信号中断,返回未休眠的秒数

setvbuf函数

项目 内容
所需头文件 #include <stdio.h>
原型 int setvbuf(FILE *stream, char *buf, int mode, size_t size);
功能 更加灵活地控制文件流的缓冲行为,允许指定缓冲模式和缓冲区的大小。注意:该函数必须在对 stream 执行任何其他操作前调用。
参数 stream 指向 FILE 对象的指针,标识要设置缓冲区的文件流。
buf 指向用户提供的缓冲区的指针。如果为 NULL,函数会自动分配一个大小为 size 的缓冲区。
mode 缓冲模式,必须是以下之一:
• _IOFBF:全缓冲
• _IOLBF:行缓冲
• _IONBF:无缓冲(当 mode 为 _IONBF 时,size 参数被忽略)
size 缓冲区的大小(以字节为单位)。
返回值 成功:返回 0
失败:返回非 0 值。

setbuf函数注意事项:

• setvbuf必须在文件流执行任何读写操作之前调用,文件流初始化后,否则可能导致设置失效,或触发未定义行为;

• 若使用自定义缓冲区(buf != NULL),需确保缓冲区大小 size 与实际需求匹配.若 size 过小,可能导致数据溢出;若 size 过大,会浪费内存资源;

• 若使用自动分配缓冲区(buf = NULL),size需设置合理值(通常建议不小于系统默认缓冲区大小,如BUFSIZ),避免因缓冲区过小影响性能或触发溢出;

• 禁止在缓冲区被释放后对文件流执行 IO 操作,否则会触发未定义行为(如访问已释放内存导致崩溃);

• 当 mode 为 _IONBF(无缓冲)时,size 参数会被忽略(即使设置非零值也无效),需避免依赖 size 配置无缓冲模式;

• 不同系统对 mode 的支持可能有差异(如某些嵌入式环境可能不支持行缓冲 _IOLBF),需根据实际平台验证行为;

二进制读写函数

fopen函数

打开文件的方式

文本模式 二进制模式 含义
r rb 以只读方式打开文件,文件不存在则报错;光标定位于文件开头。
r+ r+b 以读写方式打开文件,文件不存在则报错;光标定位于文件开头。
w wb 以只写方式打开文件,文件存在则清空,文件不存在则创建;光标定位于文件开头。
w+ w+b 以读写方式打开文件,文件存在则清空,文件不存在则创建;光标定位于文件开头。
a ab 以追加方式打开文件,文件存在保持内容不变,文件不存在则创建;光标定位于文件末尾。
a+ a+b 以读和追加方式打开文件,文件存在则保持内容不变,文件不存在则创建;读光标定位于文件开头,追加光标定位于文件末尾。

说明:

在每个选项中加入b字符用来告知函数库打开的文件为二进制文件,而非纯文本文件不过在Linux系统中会忽略该符号,加不加b字符对文件操作的实际行为没有任何影响若代码需移植到Windows,建议显式加b(如rb/wb),避免模式差异导致问题

fread函数

类别 内容
所需头文件 #include <stdio.h>
原型 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能 从 stream 中读取 nmemb 项数据,每项大小为 size,将它们存入 ptr 指向的位置中。
参数 ptr 存储读取到的数据的首地址。
size 每个数据项的大小(单位:字节)。一般将其置为 1,让项目数表示字节数。
nmemb 要读取的数据项的最大数量。
stream 文件指针。
返回值 成功:返回实际读取的数据项数量。
返回值小于 nmemb 时,可能是遇到了文件结束(EOF)或者出现了错误。
返回值为 0 时,表示没有读取到任何数据。
注意:fread 不区分文件结束(EOF)或读取错误,调用者必须使用 feof 函数或 ferror 函数来区分具体场景。

feof函数

项目 内容
所需头文件 #include <stdio.h>
原型 int feof(FILE *stream);
功能 判断是否读取到文件的结尾
参数 stream 文件指针
返回值 如果文件已到达末尾,返回一个非0值(真)
如果文件未到达末尾,返回0(假)
feof 是 “被动检测” 结尾只有当读取操作(如 fread/fgetc)实际触发了文件末尾时,标志位才会置为结束状态,文件结束标志只能通过clearerr 来清除直接用 feof 判断未读文件,返回的一定是 0(因为还没开始读,不存在 “已到结尾”)

ferror函数

项目 描述
所需头文件 #include <stdio.h>
原型 int ferror(FILE *stream);
功能 检查由 stream 指定的错误标志
参数 stream 文件指针
返回值 成功(发生了错误):真(非零);
失败(没有发生错误):假(0)
ferror 只检测 “是否发生过错误”,不主动触发错误错误标志位一旦置位,后续若不调用 clearerr 清除,ferror 会一直返回非 0,直到标志被手动清除

clearerr函数

项目 内容
所需头文件 #include <stdio.h>
原型 void clearerr(FILE *stream);
功能 清除由 stream 指定的文件结束标志和错误标志
参数 stream 文件指针
返回值
当feof检测到文件结尾后,如果想要重新读取文件,必须先调用clearerr清除结束标志,否则feof会持续返回非0;当ferror检测到错误,修复问题后,必须先调用clearerr清除错误标志,让文件流恢复可用状态;

fwrite函数

项目 描述
所需头文件 #include <stdio.h>
原型 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能 将 ptr 指向的内存数据,按 size 字节为 1 项、共 nmemb 项,写入 stream 文件流。
参数 • ptr 指向要写入数据的内存首地址
• size 每个数据项的大小(单位:字节),一般将其置为 1,让项目数表示字节数
• nmemb 要写入的数据项的最大数量
• stream 文件指针
返回值 成功时返回实际写入的数据项数量。
若返回值小于 nmemb,可能是写入错误或到达文件末尾,可通过 ferror 或 feof 函数进一步判断。

格式化输出函数

sprintf函数

项目 描述
所需头文件 #include <stdio.h>
原型 int sprintf(char *str, const char *format, ...);
功能 将 format 控制格式的内容写到 str 对应的内存中(不会向终端显示)
参数 str 内存地址
format 格式化字符串,用于定义输出的格式
... 可变参数列表
返回值 成功:返回格式化的字符的个数
失败:返回一个负数(通常是 -1)

snprintf函数

项目 说明
所需头文件 #include <stdio.h>
原型 int snprintf(char *str, size_t size, const char *format, ...);
功能 将 format 控制格式的内容写到 str 对应的内存中(不会向终端显示)
参数 str 内存地址
size 最多格式化 size 个字符,其中包含一个 '\0'
format 格式化字符串,用于定义输出的格式
... 可变参数列表
返回值 成功:返回格式化的字符个数
失败:返回一个负数(通常是 -1)

fprintf函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fprintf(FILE *stream, const char *format, ...);
功能 将控制格式化的字符串写入到文件中
参数 stream 文件指针
format 格式化字符串,用于定义输出的格式
... 可变参数列表
返回值 成功:返回格式化的字符的个数
失败:返回一个负数(通常是 -1)

获取系统时间函数

time函数

项目 内容
所需头文件 #include <time.h>
原型 time_t time(time_t *tloc);
功能 获取自 1970-01-01 00:00:00 到当前的秒数
参数  tloc 若传入非空指针,函数会把“秒数结果”同时写入该变量中;
若传入 NULL,函数仅返回秒数但不写入变量
返回值 成功:返回秒数
失败:返回错误码,并设置相应的错误码

localtime函数

类别 详细信息
所需头文件 #include <time.h>
原型 struct tm *localtime(const time_t *timep);
功能 将 time_t 类型的秒数转换为 struct tm 结构体(包含年、月、日、时、分、秒等信息)
结构体定义 struct tm {
int tm_sec; /* 秒 (0-60) */
int tm_min; /* 分 (0-59) */
int tm_hour; /* 时 (0-23) */
int tm_mday; /* 一个月中的第几天 (1-31) */
int tm_mon; /* 月 (0-11, 0 表示一月) */
int tm_year; /* 年份 - 1900 */
int tm_wday; /* 星期几 (0-6, 0 表示周日) */
int tm_yday; /* 一年中的第几天 (0-365, 0 表示 1 月 1 日) */
int tm_isdst; /* 夏令时标志 */
};
参数 timep 指向 time_t 类型变量的指针,表示从 1970‑01‑01 00:00:00 +0000 (UTC) 起经过的秒数
返回值 成功:返回指向 struct tm 结构体的指针;
失败:返回 NULL,并设置相应的错误码

文件指针控制函数

fseek函数

项目 内容
所需头文件 #include <stdio.h>
原型 int fseek(FILE *stream, long offset, int whence);
功能 设置文件流的光标位置
参数 stream 文件指针
offset

光标的偏移量

>0:向后偏移;

=0:不偏移;

<0:向前偏移;

whence

偏移起始位置

SEEK_SET:从文件开头偏移;

SEEK_CUR:从当前位置偏移;

SEEK_END:从文件结尾偏移;

返回值 成功:返回 0
失败:返回 -1,并设置相应的错误码

rewind函数

项目 内容
所需头文件 #include <stdio.h>
原型 void rewind(FILE *stream);
功能 将光标恢复到文件的开头
参数 stream 文件指针
返回值

ftell函数

项目 内容
所需头文件 #include <stdio.h>
原型 long ftell(FILE *stream);
功能 返回文件流当前位置相对于文件起始位置的偏移量(以字节为单位)
参数 stream 文件指针
返回值 成功 返回字节数
失败 返回 -1,并设置相应的错误码

文件IO介绍

文件IO是在POSIX(可移植操作系统接口)中定义的一组输入输出的函数,涉及操作系统和应用程序之间的交互,用于读取和写入存储在永久性存储介质(如硬盘、SSD、USB闪存盘等)上的数据;

跨平台性:不同操作系统(如UNIX、Windows、Linux)的文件IO机制存在差异,接口、调用方式和底层实现逻辑各有特点;

如Windows下有CreateFile等专属函数,Linux则基于open、read等系统调用;

高效实现原理:

• 直接交互硬件/文件系统:绕过高级缓冲,直接发起系统调用访问磁盘等设备,数据传输路径短,适合对实时性、精准控制要求高的场景(如设备驱动开发);

• 无用户态缓冲:不依赖用户态缓冲机制,每次读写更贴近硬件真实操作,但频繁调用会增加系统开销,需开发者按需优化(如手动合并读写请求);

易用性设计:

• 贴近底层逻辑:需开发者熟悉操作系统 API、文件权限、设备特性等,直接处理缓冲区管理、错误码解析(如Linux中errno),对开发经验要求高;

• 灵活但复杂:可精准控制文件打开模式(只读、读写等)、数据传输细节,却也因需手动处理资源释放(如close函数)、并发冲突等问题,增加开发复杂度;

特点:

• 没有缓冲机制,每次调用都会引起系统调用;

• 围绕文件描述符进行操作,相当于标准IO中的流(FILE *),文件描述符是正整数;

• 默认打开三个描述符:标准输入(0)标准输出(1)标准错误(2)•除目录文件,其他类型文件都可以操作;

• 可移植性相对较弱;

与标准IO对比

对比维度 文件 IO 标准 IO
概念 对文件系统中的文件进行读写 程序与终端(控制台)的交互
用途 持久化存储、跨程序数据交换 即时交互、简单调试
操作对象 磁盘文件(需路径或描述符) 以流为抽象,覆盖终端和磁盘文件
数据流向 程序 ⇄ 磁盘(持久化) 程序 ⇄ 终端(临时交互)
缓冲机制 默认缓冲 stdout:行缓冲
stderr:无缓冲
错误处理 需显式处理文件不存在、权限不足等 通常无需特殊处理(终端直接反馈)
典型场景 配置文件读写、日志记录、数据存储等 命令行参数输入、结果显示、错误提示等
性能特点 大文件或批量读写时,通过手动优化(调整读写块大小)更高效 频繁小数据读写时,标准 IO 的缓冲机制更高效
生命周期 数据持久保存 会话结束后数据丢失

文件描述符

文件描述符是操作系统内核为每个打开的文件/设备分配的唯一整数标识符,用于标识和跟踪进程与文件的交互,在Linux/Unix中,它是底层文件IO操作的核心参数;

本质:

• 它是指向内核中维护的文件表(File Table),该表中记录了文件的状态,位置指针等信息;

• 每个进程都有自己独立的文件描述符表,通常是从0开始的编号,相互不干扰;Linux系统中一个正在执行的程序中文件描述符的范围是[0-1023],可通过ulimit调整,一般使用默认值;

作用:

• 连接用户进程与内核资源:通过文件描述符,进程可以请求内核进行文件读写、设备操作等,无需关心底层实现细节;

• 资源管理:操作系统通过文件描述符跟踪哪些文件被哪些进程打开,并在进程关闭文件或退出时自动回收资源;

特点:

• 唯一性:同一个进程内,每个文件描述符对应唯一的打开文件;

• 非负整数:通常是小整数(如0、1、2),但可动态分配(如 open()返回的新描述符);

• 跨函数传递:可在不同系统调用间传递,代表对同一文件的持续操作;

常见文件描述符编号:

编号 宏定义 用途 对应设备
0 STDIN_FILENO 标准输入(Standard Input) 通常是键盘
1 STDOUT_FILENO 标准输出(Standard Output) 通常是终端屏幕
2 STDERR_FILENO 标准错误(Standard Error) 通常是终端屏幕

文件打开与关闭函数

open函数

项目 内容
所需头文件 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
原型 int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能 用文件 I/O 的方式打开文件
参数 pathname 文件路径和名字。不写路径默认打开当前路径下的文件
flags 打开文件的方式:
• O_RDONLY:以只读方式打开,文件不存在则失败
• O_WRONLY:以只写方式打开,文件不存在则失败
• O_RDWR:以读写方式打开,文件不存在则失败
• O_APPEND:以追加方式打开,文件不存在则失败
• O_CREAT:文件不存在则创建文件。若使用此标志,必须填写第三个参数 mode(创建文件的权限)
• O_TRUNC:如果文件存在则清空
• O_EXCL:与 O_CREAT 结合使用。若文件不存在则创建;若文件存在则报错,错误码为 EEXIST
mode 创建文件的权限,一般用八进制表示。实际创建出来的文件权限 = (mode & ~umask)。
示例:umask 默认值 0002,最大文件权限 0666,则实际权限 = (0666 & ~0002) = (0666 & 0664) = 0664
返回值 成功:返回文件描述符
失败:返回 -1,错误码 errno 会被正确设置

标准IO和文件IO打开方式对比'

文件IO 标准IO 功能描述
O_RDONLY "r" 以只读的方式打开文件,将光标定位到开头
O_RDWR "r+" 以读写的方式打开文件,将光标定位在开头
O_WRONLY | O_CREAT | O_TRUNC, 0666 "w" 以只写的方式打开文件:如果文件不存在就创建;如果文件存在则清空,光标在开头
O_RDWR | O_CREAT | O_TRUNC, 0666 "w+" 以读写的方式打开文件:如果文件不存在就创建;如果文件存在则清空,光标在开头
O_WRONLY | O_APPEND | O_CREAT, 0666 "a" 以追加的方式打开文件:如果文件不存在就创建;如果存在则光标在结尾
O_RDWR | O_APPEND | O_CREAT, 0666 "a+" 以读和追加的方式打开文件:如果文件不存在就创建;如果存在,读光标始终在开头,写光标始终在结尾

close函数

类别 内容
所需头文件 #include <unistd.h>
原型 int close(int fd);
功能 用文件IO的方式关闭文件
参数 fd 文件描述符
返回值 成功:返回0
失败:返回-1,错误码errno会被正确设置

文件读写函数

read函数

项目 内容
所需头文件 #include <unistd.h>
原型 ssize_t read(int fd, void *buf, size_t count);
功能 用文件IO的方式读取文件中的内容
参数 fd 文件描述符
buf 存储读取到的数据的首地址
count 想要读取的字节的个数
返回值 成功 返回读取到的字节的个数;返回0表示读取到文件的结尾
失败 返回-1,错误码errno会被正确设置

write函数

项目 内容
所需头文件 #include <unistd.h>
原型 ssize_t write(int fd, const void *buf, size_t count);
功能 用文件IO的方式往文件中写入内容
参数 fd 文件描述符
buf 想要写入数据的首地址
count 想要写入的字节的个数
返回值 - 成功:返回写入的字节的个数;返回0表示不会写任何的数据
- 失败:返回-1,错误码errno会被正确设置

文件定位与控制函数

lseek函数

项目 说明
所需头文件 #include <sys/types.h>
#include <unistd.h>
原型 off_t lseek(int fd, off_t offset, int whence);
功能 定位光标的位置(光标实际指文件偏移量)
参数 fd 文件描述符
offset 偏移量
 >0:向后偏移
 =0:不偏移
 <0:向前移动
whence 从哪个位置开始偏移
SEEK_SET:从文件的开头
SEEK_CUR:从当前位置
SEEK_END:从文件的结尾
注意:若文件以 O_APPEND 打开,write 会强制将偏移量置为文件末尾,lseek 调整的偏移量仅对 read 生效
返回值 成功:返回从文件开头计算出的结果偏移量(单位:字节)
失败:返回 -1,错误码 errno 会被正确设置

fcntl函数

项目 内容
所需头文件 #include <unistd.h>
#include <fcntl.h>
原型 int fcntl(int fd, int cmd, ... /* arg */);
功能 对打开的文件描述符进行多种控制(如复制文件描述符、设置和获取文件描述符标志、实现文件锁等等)
参数 fd 文件描述符
cmd 指定要执行的控制操作命令
 - F_DUPFD:复制描述符
 - F_GETFD:获取描述符标志
 - F_SETFL:设置文件状态标志(arg 是整数,如 O_NONBLOCK
 - F_GETLK:获取锁信息(arg 是 struct flock *
 - F_SETLK:尝试加锁(非阻塞,arg 是 struct flock *
 - F_SETLKW:阻塞加锁(arg 是 struct flock *
 - ...
... 可选参数,是否必填由 cmd 决定
返回值 F_DUPFD:成功返回新描述符,失败返回 -1
F_GETFD / F_GETFL / F_GETLK:成功返回对应值,失败返回 -1
F_SETFD / F_SETFL / F_SETLK / F_SETLKW:成功返回 0,失败返回 -1

struct flock结构体

struct flock {
    short l_type;      // 锁类型:F_RDLCK(读锁)、F_WRLCK(写锁)、F_UNLCK(解锁)
    short l_whence;    // 锁的基准位置(同 lseek 的 whence:SEEK_SET / SEEK_CUR / SEEK_END)
    __kernel_off_t l_start;  // 锁的起始偏移量
    __kernel_off_t l_len;    // 锁的长度(0 表示到文件末尾)
    __kernel_pid_t l_pid;    // 持有锁的进程 ID(仅 F_GETLK 有效)
};

文件元信息查询函数

stat函数

项目 内容
所需头文件 #include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
原型 int stat(const char *pathname, struct stat *statbuf);
功能 获取文件的属性信息
参数 pathname 文件路径和名字。不写路径默认打开当前路径下的文件。如果是符号链接,会跟随链接,获取最终指向的“目标文件”的属性。
statbuf 用于保存文件属性信息的内存空间的首地址。
返回值 成功:返回0
失败:返回-1,错误码errno会被正确设置

stat结构体

struct stat {
    dev_t     st_dev;     /* 磁盘设备号 */
    ino_t     st_ino;     /* 文件的inode号,文件系统识别文件的唯一编号,通过ls -i查看 */
    mode_t    st_mode;    /* 文件模式:文件权限及类型 */
    nlink_t   st_nlink;   /* 硬链接个数 */
    uid_t     st_uid;     /* 所属用户的id */
    gid_t     st_gid;     /* 所属组的id */
    dev_t     st_rdev;    /* 设备id(字符设备或者是块设备) */
    off_t     st_size;    /* 文件的总大小,单位是字节 */
    blksize_t st_blksize; /* 文件系统IO块的大小 */
    blkcnt_t  st_blocks;  /* 块的个数 */
    struct timespec st_atim; /* 最后一次访问的时间 */
    struct timespec st_mtim; /* 最后一次被修改的时间 */
    struct timespec st_ctim; /* 最后一次状态改变的时间 */
};

/* 方便访问时间戳中秒数的宏定义 */
#define st_atime st_atim.tv_sec
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec

文件的权限及类型

文件权限的位域(0-8位):st_mode 的低 9 位(第 0-8 位 )分为三组,每组 3 位:

• 第 0-2 位:其他用户(others)的读、写、执行权限;

• 第 3-5 位:组用户(group)的读、写、执行权限;

• 第 6-8 位:所有者(user)的读、写、执行权限;

文件类型的位域(12-15位):st_mode的高4位用于标识文件类型,用__S_IFMT作为“掩码”提取这4位以下是一些宏定义:

#define __S_IFMT   0170000   /* 这些位决定了文件的类型 */

#define __S_IFDIR  0040000   /* 目录文件 */
#define __S_IFCHR  0020000   /* 字符设备文件 */
#define __S_IFBLK  0060000   /* 块设备文件 */
#define __S_IFREG  0100000   /* 普通文件 */
#define __S_IFIFO  0010000   /* 管道文件 */
#define __S_IFLNK  0120000   /* 软链接文件 */
#define __S_IFSOCK 0140000   /* 套接字文件 */

lstat函数

类别 内容
所需头文件 #include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
原型 int lstat(const char *pathname, struct stat *statbuf);
功能 获取文件的属性信息
参数 pathname 文件路径和名字。不写路径默认打开当前路径下的文件。如果是符号链接,不会跟随链接,直接获取“符号链接本身”的属性。
statbuf 用于保存文件属性信息的内存空间的首地址
返回值 成功:返回 0
失败:返回 -1,错误码 errno 会被正确设置

fstat函数

项目 内容
所需头文件 #include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
原型 int fstat(int fd, struct stat *statbuf);
功能 通过文件描述符获取已打开文件的属性信息
参数 fd 文件描述符
statbuf 用于保存文件属性信息的内存空间的首地址
返回值 成功:返回 0
失败:返回 -1,错误码 errno 会被正确设置

目录遍历操作函数

opendir函数

项目 内容
所需头文件 #include <sys/types.h>
#include <dirent.h>
原型 DIR *opendir(const char *name);
功能 打开目录
参数 name 目录名
返回值 成功:返回目录的指针
失败:返回 NULL,错误码 errno 会被正确设置

closedir函数

项目 内容
所需头文件 #include <sys/types.h>
#include <dirent.h>
原型 int closedir(DIR *dirp);
功能 关闭目录
参数 dirp 目录的指针
返回值 成功:返回0
失败:返回-1,错误码 errno 会被正确设置

readdir函数

项目 内容
所需头文件 #include <dirent.h>
原型 struct dirent *readdir(DIR *dirp);
功能 读取目录下的文件
参数 dirp 目录指针
返回值 成功:返回 dirent 结构体指针;
失败:到了目录的结尾返回 NULL,错误码 errno 会被正确设置

dirent结构体

struct dirent {
    ino_t          d_ino;      /* 该目录项对应的文件/目录的 inode 编号 */
    off_t          d_off;      /* 当前目录项在目录流中的偏移量 */
    unsigned short d_reclen;   /* 当前目录项记录的长度(字节数) */
    unsigned char  d_type;     /* 文件类型----但是并不支持所有文件系统 */
    char           d_name[256];/* 文件名 */
};

/* d_type 可能的取值(宏定义) */
#define DT_BLK    /* 块设备文件 */
#define DT_CHR    /* 字符设备文件 */
#define DT_DIR    /* 目录 */
#define DT_FIFO   /* 命名管道(FIFO) */
#define DT_LNK    /* 符号链接 */
#define DT_REG    /* 普通文件 */
#define DT_SOCK   /* UNIX套接字 */

用户/组ID映射函数

getpwuid函数

项目 内容
所需头文件 #include <sys/types.h>
#include <pwd.h>
原型 struct passwd *getpwuid(uid_t uid);
功能 根据 uid 获取用户信息结构体
参数 uid 用户的 id
返回值 成功:返回用户信息的结构体指针
失败:返回 NULL,错误码 errno 会被正确设置

passwd结构体

struct passwd {
    char *pw_name;      /* 用户名 */
    char *pw_passwd;    /* 是否有密码 */
    uid_t pw_uid;       /* 用户ID */
    gid_t pw_gid;       /* 组ID */
    char *pw_gecos;     /* 描述信息 */
    char *pw_dir;       /* 家目录 */
    char *pw_shell;     /* 命令行解析器 */
};

getgrgid函数

项目 内容
所需头文件 #include <sys/types.h>
#include <grp.h>
原型 struct group *getgrgid(gid_t gid);
功能 根据 gid 获取组结构体
结构体定义
struct group {
    char *gr_name;      // 组名
    char *gr_passwd;    // 组的密码
    __gid_t gr_gid;     // 组号
    char **gr_mem;      // 组成员列表
};
参数 gid 组号
返回值 成功:返回结构体指针
失败:返回 NULL,错误码 errno 会被正确设置

模拟Linux命令 ls 的实现

命令ls的实现

• 练习要求:模拟ls -l基础功能,查看单个文件的详细信息

linux@ubuntu:~$ ls -l a
-rw-rw-- 1linux linux 0 Jul 15 17:11 a
linux@ubuntu:~$

实现思路:

• 对输入参数的参数个数进行检查;

• 获取目标文件的属性,如果获取失败,可以通过perror输出错误信息并退出;

• 对文件类型与权限进行解析;

• 其他信息输出(硬链接数、文件所有者、文件所属组、文件大小、修改时间、文件名);

strftime函数

项目 内容
所需头文件 #include <time.h>
原型 size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
功能 strftime函数可以将 struct tm 结构体所表示的时间,按照指定的格式字符串,转化为一个格式化后的字符串。
参数 s 指向用于存储格式化后时间字符串的字符数组指针。
max 表示字符数组 s 的最大长度,即最多可以写入 max - 1 个字符。
format 指向一个格式字符串,该字符串包含普通字符以及转换说明符(以 % 开头)。
tm 指向 struct tm 结构体的指针,struct tm 结构体中包含分解后的时间信息,例如年、月、日等。
返回值 成功:返回写入到字符数组 s 中的字符数(不包括字符串结束符 \0)。
失败:返回 0,表示没有成功将时间格式化并写入到指定的字符数组中。

日期相关格式

格式说明符 含义 示例(以 2024 年 7 月 15 日为例)
%Y 四位数的年份 2024
%y 两位数的年份 24
%m 月份,数字表示,补前导零(01 - 12) 07
%b 本地语言环境下的月份缩写,英文环境下如 Jan 等 Jul(英文环境)
%B 本地语言环境下的月份全称,英文环境下如 January 等 July(英文环境)
%d 月份中的日期,数字表示,补前导零(01 - 31) 15
%e 月份中的日期,数字表示,不补前导零(1 - 31) 15
%a 本地语言环境下的星期几缩写,英文环境下如 Mon 等 Mon(假设 7 月 15 日是周一,英文环境)
%A 本地语言环境下的星期几全称,英文环境下如 Monday 等 Monday(假设 7 月 15 日是周一,英文环境)
%j 一年中的第几天,数字表示,补前导零(001 - 366) 197(2024 年 7 月 15 日是一年第 197 天)

时间相关格式

格式说明符 含义 示例(以 14:30:25 为例)
%H 24 小时制的小时,数字表示,补前导零(00 - 23) 14
%I 12 小时制的小时,数字表示,补前导零(01 - 12) 02
%k 24 小时制的小时,数字表示,不补前导零(0 - 23) 14
%l 12 小时制的小时,数字表示,不补前导零(1 - 12) 2
%M 分钟,数字表示,补前导零(00 - 59) 30
%S 秒,数字表示,补前导零(00 - 59) 25
%p 本地语言环境下的上午或下午标识,英文环境下为 AM 或 PM PM(14 点对应下午)

库文件介绍

定义:库文件是一组预先编译好的函数、类或数据的集合,以文件的形式存在,用于被其他程序调用;

本质:库文件是一种可执行代码的二进制形式,是代码复用的一种方式,将常用功能封装起来,方便开发者在不同项目中使用;

作用:

• 提高开发效率:避免重复编写相同功能的代码,减少开发时间;

• 便于代码维护:集中管理功能模块,修改和升级库时,只需更新库文件,无需修改使用库的程序;

• 保护代码隐私:可以将核心代码封装在库中,只提供接口供外部使用,隐藏实现细节;

库的分类:静态库和共享库(动态库)

在不同平台中都存在大量的库,不同平台(如 Linux vs Windows)的指令集、二进制格式、系统调用不同,导致库无法跨平台直接使用(比如 Linux 的 .so 不能在 Windows 上运行);

linux平台下库文件命名:

• 静态库命名规则为:libxxx.a(以lib为前缀+自定义库名+.a后缀);

• 动态库命名规则为:libxxx.so(以lib为前缀+自定义库名+.so后缀);

若涉及到版本管理,动态库命名可扩展为:libxxx.so.x.y.z

x 为主版本号,不兼容旧版;y 为次版本号,兼容旧版;z 为发行版本号,修复问题不影响接口

静态库介绍及制作

定义:静态库是在程序编译链接阶段,其代码会被完整地”复制”到最终生成的可执行文件中,成为可执行文件的一部分,编译完成后,静态库与可执行文件“绑定”,运行时无需额外依赖;

特点:

• 可执行文件独立运行,不依赖外部库文件;

• 静态库会使可执行文件体积较大,但是效率比较高;

• 若静态库更新,需重新编译链接程序;

静态库文件格式:

• windows平台下为.lib;Linux平台下为.a;Mac平台下为.a;

静态库制作及使用

准备源文件:编写实现库功能的源文件(如 .c或 .cpp);

编译生成目标文件:使用编译器将源文件编译成目标文件(如 .o或.obj);

示例:gcc -c源文件.c -o目标文件.o

打包生成静态库:使用归档工具将目标文件打包成静态库;

示例:ar-cr静态库.a 目标文件.o

编译主程序并链接静态库,生成可执行文件;

示例:gcc目标文件.o -l库名 -L库路径 -o 可执行文件

说明:如果库名是libmyadd.a,则库名是myadd,编译器会自动拼接lib前缀和.a后缀进行查找库文件;

静态库所用核心选项

工具 选项 作用 示例命令
GCC -c 只编译不链接,生成目标文件(.o) gcc -c add.c -o add.o
-o 指定输出文件名称 gcc -c main.c -o main.o
-l 指定要链接的库名(省略lib前缀和.a后缀) gcc main.c -lmyadd -o main
-L 指定库文件搜索路径 gcc main.c -L./lib -lmyadd -o main
AR -c 创建新的归档文件(静态库) ar -cr libmyadd.a add.o
-r 将目标文件添加到归档文件中(替换已存在的文件) ar -cr libmyadd.a add.o

动态库介绍及制作

定义:动态库也称之为共享库,是在程序运行时才被加载到内存中,可以被多个可执行文件共享使用,为它们提供函数、数据等资源;

特点:

• 可执行文件运行时,需依赖系统中存在的动态库;

• 可执行文件体积小,有效节省磁盘和内存空间;

• 更新灵活,只要接口不变,无需重新编译依赖它的可执行文件;

动态库文件格式:

• windows平台下为.dll;Linux平台下为.so;Mac平台下为.dylib;

动态库制作及使用

准备源文件:编写实现库功能的源文件(如 .c或 .cpp);

编译生成与位置无关的目标文件:

使用编译器编译源文件时,添加相关选项(如GCC的-fPIC选项)生成位置无关代码(PIC)的目标文件;

示例:gcc -c -fPIC 源文件.c -o目标文件.o

链接生成动态库:使用编译器将目标文件链接为动态库,多个程序可共享同一份动态库文件,节省内存;

示例:gcc -shared 目标文件.o -o 动态库.so

编译时通过-L指定路径、-l指定库名生成可执行文件;

示例:gcc 源文件.c -l库名 -L库路径 -o 可执行文件

运行时需通过LD_LIBRARY_PATH环境变量、/etc/ld.so.conf配置或系统路径,保证动态库可被加载;

示例1:通过LD_LIBRARY_PATH环境变量运行;

export LD_LIBRARY_PATH=$PWD(=后面就是库路径,PWD表示当前路径)

示例2:通过/etc/ld.so.conf 配置运行;

编辑配置文件添加动态库路径:sudo vim /etc/ld.so.conf(在此文件中添加动态库路径)

添加完成之后,执行sudo ldconfig命令更新系统缓存

示例3:通过系统路径运行:优先考虑/usr/local/lib路径,然后进行更新系统缓存

系统级库放/usr/lib,用户自定义库放 /usr/local/lib;

动态库所用核心选项

工具 选项 作用 示例
gcc(编译阶段) -c 只编译不链接,生成目标文件(.o),不生成可执行程序 gcc -c add.c -o add.o
-fPIC 生成位置无关代码(PIC),确保动态库可被加载到内存任意地址并正常运行(动态库必需) gcc -c -fPIC add.c -o add.o
-o 指定输出文件名称(如目标文件.o或动态库.so) gcc -c -fPIC add.c -o add.o
gcc(链接阶段) -shared 将目标文件(.o)链接为动态库(.so),是生成动态库的核心选项 gcc -shared add.o -o libmyadd.so
gcc(使用阶段) -L 指定动态库的搜索路径(编译时用于查找 .so 文件) gcc main.c -lmyadd -L./lib -o main
-l 指定要链接的动态库名称(省略lib前缀和.so后缀;如libmyadd.so 对应 -lmyadd) gcc main.c -lmyadd -L. -o main
系统工具 ldconfig 更新系统动态库缓存,使新添加的动态库(如放入/usr/lib)被系统识别(需sudo权限) sudo ldconfig

静态库与动态库的区别

链接方式:静态库在编译时链接,动态库在运行时链接;

可执行文件体积:静态库使可执行文件体积大,动态库使可执行文件体积小;

内存占用:多个程序使用同一静态库时,每个程序都有一份库的副本,占用内存多;

多个程序共享同一动态库时,内存中只有一份库的副本,节省内存;

更新维护:静态库更新需重新编译链接程序;动态库更新只需替换库文件,无需重新编译链接程序;

依赖性:静态库无运行时依赖;动态库有运行时依赖,库缺失程序无法运行;

适用场景:

• 静态库:适合对程序独立性要求高,库不常更新,且对可执行文件启动速度有要求的场景;

• 动态库:适合库需要频繁更新,多个程序共享库功能,且希望减小可执行文件体积的场景;

库制作常见问题及解决方案

静态库链接时提示未定义的引用:

• 可能原因:库文件路径错误、库名拼写错误、库中确实不存在该函数或变量的定义;

• 解决方法:检查库路径和库名是否正确,确认库中是否实现了所需功能;

动态库运行时提示无法找到库文件:

• 可能原因:库文件路径未加入系统默认搜索路径或者是未通过环境变量指定;

• 解决方法:将动态库复制到系统默认库路径(如/usr/local/lib),或设置相应的环境变量(如Linux的LD_LIBRARY_PATH);

动态库版本冲突:

• 可能原因:程序依赖的动态库版本与系统中安装的版本不一致;

• 解决方法:安装程序所需版本的动态库,或重新编译程序使其适配系统中的库版本;

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐