大致自定义文件I/O库函数的实现详解(了解即可)

目录

一、mystdio.h

代码思路分析

二、mystdio.c

[1. 辅助函数 BuyFile](#1. 辅助函数 BuyFile)

[2. 文件打开函数 MyFopen](#2. 文件打开函数 MyFopen)

[3. 文件关闭函数 MyFclose](#3. 文件关闭函数 MyFclose)

[4. 数据写入函数 MyFwrite](#4. 数据写入函数 MyFwrite)

[1、memcpy(file->outbuffer + file->bufferlen, str, len);](#1、memcpy(file->outbuffer + file->bufferlen, str, len);)

2、按位与(&)运算的作用

运算规则

[5. 缓冲区刷新函数 MyFFlush](#5. 缓冲区刷新函数 MyFFlush)

1、缓冲区空检查

2、数据写入系统调用

3、数据同步到磁盘

4、重置缓冲区

三、Makefile

四、Makefile-st


一、mystdio.h

cpp 复制代码
#pragma once
// 这是一个预处理指令,确保头文件在编译时只被包含一次,防止重复包含

#include <stdio.h>
// 包含标准输入输出库,提供基本的文件操作函数原型

#define MAX 1024
// 定义输出缓冲区的大小为1024字节

// 定义刷新模式的标志位
#define NONE_FLUSH (1<<0)   // 不自动刷新
#define LINE_FLUSH (1<<1)   // 行缓冲模式(遇到换行符时刷新)
#define FULL_FLUSH (1<<2)   // 全缓冲模式(缓冲区满时刷新)

// 自定义文件结构体,模拟标准库的FILE结构
typedef struct IO_FILE
{
    int fileno;             // 文件描述符(底层操作系统文件标识)
    int flag;               // 文件打开模式标志
    char outbuffer[MAX];    // 输出缓冲区
    int bufferlen;          // 当前缓冲区中的数据长度
    int flush_method;       // 缓冲刷新策略(NONE/LINE/FULL)
}MyFile;


// 函数声明

/**
 * @brief 打开文件并初始化MyFile结构体
 * @param path 文件路径
 * @param mode 打开模式("r"/"w"/"a"等)
 * @return 成功返回MyFile指针,失败返回NULL
 */
MyFile *MyFopen(const char *path, const char *mode);

/**
 * @brief 关闭文件并释放资源
 * @param fp MyFile指针
 */
void MyFclose(MyFile *fp);

/**
 * @brief 向文件写入数据
 * @param fp MyFile指针
 * @param str 要写入的数据指针
 * @param len 要写入的数据长度
 * @return 成功写入的字节数
 */
int MyFwrite(MyFile *fp, void *str, int len);

/**
 * @brief 手动刷新缓冲区
 * @param fp MyFile指针
 */
void MyFFlush(MyFile *fp);

代码思路分析

这段代码实现了一个简单的文件I/O封装库,模拟了标准C库中的文件操作函数。主要特点包括:

  1. 缓冲机制

    • 使用outbuffer作为输出缓冲区,减少直接系统调用的次数

    • 支持三种缓冲策略:无缓冲、行缓冲和全缓冲

  2. 结构设计

    • MyFile结构体封装了文件描述符、缓冲区及状态信息

    • 类似于标准库的FILE结构,但更简化

  3. 功能模拟

    • MyFopen:模拟fopen,打开文件并初始化结构体

    • MyFwrite:模拟fwrite,写入数据到缓冲区

    • MyFFlush:模拟fflush,强制刷新缓冲区

    • MyFclose:模拟fclose,关闭文件并释放资源

  4. 缓冲策略

    • NONE_FLUSH:每次写入都立即刷新(无缓冲)

    • LINE_FLUSH:遇到换行符时刷新(行缓冲)

    • FULL_FLUSH:缓冲区满时刷新(全缓冲)


二、mystdio.c

cpp 复制代码
#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

static MyFile *BuyFile(int fd, int flag)
{
    MyFile *f = (MyFile*)malloc(sizeof(MyFile));
    if(f == NULL) return NULL;
    f->bufferlen = 0;
    f->fileno = fd;
    f->flag = flag;
    f->flush_method = LINE_FLUSH;
    memset(f->outbuffer, 0, sizeof(f->outbuffer));
    return f;
}

MyFile *MyFopen(const char *path, const char *mode)
{
    int fd = -1;
    int flag = 0;
    if(strcmp(mode, "w") == 0)
    {
        flag = O_CREAT | O_WRONLY | O_TRUNC;
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flag = O_CREAT | O_WRONLY | O_APPEND;
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "r") == 0)
    {
        flag = O_RDWR;
        fd = open(path, flag);
    }
    else
    {
        //TODO
    }
    if(fd < 0) return NULL;
    return BuyFile(fd, flag);
}
void MyFclose(MyFile *file)
{
    if(file->fileno < 0) return;
    MyFFlush(file);
    close(file->fileno);
    free(file);
}
int MyFwrite(MyFile *file, void *str, int len)
{
    // 1. 拷贝
    memcpy(file->outbuffer+file->bufferlen, str, len);
    file->bufferlen += len;
    // 2. 尝试判断是否满足刷新条件!
    if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n')
    {
        MyFFlush(file);
    }
    return 0;
}
void MyFFlush(MyFile *file)
{
    if(file->bufferlen <= 0) return;
    // 把数据从用户拷贝到内核文件缓冲区中
    int n = write(file->fileno, file->outbuffer, file->bufferlen);
    (void)n;
    fsync(file->fileno);
    file->bufferlen = 0;

}

1. 辅助函数 BuyFile

BuyFile 函数在这个自定义 I/O 库中模拟的是标准 C 库(如 glibc)中 FILE 结构体的初始化过程。

cpp 复制代码
/**
 * @brief 分配并初始化MyFile结构体
 * @param fd 文件描述符
 * @param flag 文件打开标志
 * @return 成功返回MyFile指针,失败返回NULL
 */
static MyFile *BuyFile(int fd, int flag)
{
    // 分配MyFile结构体内存
    MyFile *f = (MyFile*)malloc(sizeof(MyFile));
    if(f == NULL) return NULL;
    
    // 初始化结构体成员
    f->bufferlen = 0;           // 缓冲区初始为空
    f->fileno = fd;             // 设置文件描述符
    f->flag = flag;             // 设置文件打开标志
    f->flush_method = LINE_FLUSH; // 默认使用行缓冲模式
    memset(f->outbuffer, 0, sizeof(f->outbuffer)); // 清空缓冲区
    
    return f;
}

功能分析

  • 这是一个静态辅助函数,用于创建和初始化MyFile结构体

  • 设置默认缓冲策略为LINE_FLUSH(行缓冲)

  • 清空输出缓冲区,确保初始状态干净

2. 文件打开函数 MyFopen

cpp 复制代码
/**
 * @brief 打开文件并初始化MyFile对象
 * @param path 文件路径
 * @param mode 打开模式("w"/"a"/"r")
 * @return 成功返回MyFile指针,失败返回NULL
 */
MyFile *MyFopen(const char *path, const char *mode)
{
    int fd = -1;
    int flag = 0;
    
    // 根据模式设置打开标志
    if(strcmp(mode, "w") == 0)       // 写模式
    {
        flag = O_CREAT | O_WRONLY | O_TRUNC; // 创建文件/只写/清空内容
        fd = open(path, flag, 0666);  // 设置文件权限为rw-rw-rw-
    }
    else if(strcmp(mode, "a") == 0)  // 追加模式
    {
        flag = O_CREAT | O_WRONLY | O_APPEND; // 创建文件/只写/追加
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "r") == 0)  // 读模式
    {
        flag = O_RDWR;               // 读写模式
        fd = open(path, flag);
    }
    else
    {
        //TODO: 可以添加其他模式支持
    }
    
    // 检查文件是否成功打开
    if(fd < 0) return NULL;
    
    // 创建并初始化MyFile对象
    return BuyFile(fd, flag);
}

功能分析

  • 支持三种基本文件模式:写("w")、追加("a")和读("r")

  • 使用系统调用open打开文件,获取文件描述符

  • 根据模式设置不同的打开标志:

    • "w"模式会清空文件内容

    • "a"模式会在文件末尾追加

    • "r"模式允许读写

  • 最终调用BuyFile创建并初始化MyFile对象

3. 文件关闭函数 MyFclose

cpp 复制代码
/**
 * @brief 关闭文件并释放资源
 * @param file MyFile对象指针
 */
void MyFclose(MyFile *file)
{
    if(file->fileno < 0) return;  // 检查文件描述符是否有效
    
    MyFFlush(file);      // 确保缓冲区数据写入文件
    close(file->fileno); // 关闭文件描述符
    free(file);          // 释放MyFile结构体内存
}

功能分析

  • 首先刷新缓冲区,确保所有数据写入文件

  • 关闭底层文件描述符

  • 释放MyFile结构体内存

  • 包含安全检查,防止无效文件描述符

4. 数据写入函数 MyFwrite

cpp 复制代码
/**
 * @brief 向文件写入数据
 * @param file MyFile对象指针
 * @param str 要写入的数据指针
 * @param len 要写入的数据长度
 * @return 总是返回0(实际实现可能需要改进)
 */
int MyFwrite(MyFile *file, void *str, int len)
{
    // 1. 将数据拷贝到缓冲区
    memcpy(file->outbuffer + file->bufferlen, str, len);
    file->bufferlen += len;  // 更新缓冲区长度
    
    // 2. 检查是否满足刷新条件
    if((file->flush_method & LINE_FLUSH) &&      // 如果是行缓冲模式
       file->outbuffer[file->bufferlen-1] == '\n') // 且最后一个字符是换行符
    {
        MyFFlush(file);  // 刷新缓冲区
    }
    
    return 0;  // 注意:实际应该返回写入的字节数,这里需要改进
}

功能分析

1、memcpy(file->outbuffer + file->bufferlen, str, len);

cpp 复制代码
memcpy(file->outbuffer + file->bufferlen, str, len);
  • file->outbuffer:指向文件输出缓冲区的起始地址。

  • file->bufferlen:当前缓冲区中已存储的数据长度(单位:字节)。

  • file->outbuffer + file->bufferlen :计算缓冲区中下一个空闲位置的地址(即已存数据的末尾)。

  • str:要写入的数据源地址(用户传入的字符串或二进制数据)。

  • len:要拷贝的数据长度(单位:字节)。

  • memcpy :标准内存拷贝函数,将 strlen 字节数据复制到目标地址。

2、按位与(&)运算的作用

  • LINE_FLUSH 的定义(在头文件中):

    cpp 复制代码
    #define LINE_FLUSH (1 << 1)  // 即二进制 0b10(十进制 2)
  • file->flush_method 是一个整型变量,存储当前的缓冲模式(可能是 NONE_FLUSHLINE_FLUSHFULL_FLUSH 的组合)。

运算规则

  • 按位与 & 会对两个数的 二进制位 逐位比较:

    • 如果某一位在两个数中都是 1,结果的该位才是 1,否则是 0
  • 判断逻辑

    • 如果 file->flush_method 包含 LINE_FLUSH,则 file->flush_method & LINE_FLUSH 结果非零(true

    • 如果不包含,则结果为 0false)。

  • 将数据直接拷贝到输出缓冲区

  • 实现了行缓冲逻辑:当检测到换行符时自动刷新

  • 当前实现总是返回0,实际应该返回写入的字节数

  • 缺少缓冲区满时的处理逻辑(全缓冲模式)

5. 缓冲区刷新函数 MyFFlush

cpp 复制代码
/**
 * @brief 刷新缓冲区,将数据写入文件
 * @param file MyFile对象指针
 */
void MyFFlush(MyFile *file)
{
    if(file->bufferlen <= 0) return;  // 缓冲区为空则直接返回
    
    // 将数据从用户缓冲区写入内核缓冲区
    int n = write(file->fileno, file->outbuffer, file->bufferlen);
    (void)n;  // 忽略返回值(实际实现应该检查)
    
    fsync(file->fileno);  // 确保数据写入磁盘
    
    file->bufferlen = 0;  // 重置缓冲区长度
}

功能分析

  • 将缓冲区内容写入底层文件描述符

  • 使用fsync确保数据持久化到磁盘

  • 重置缓冲区长度为0

  • 当前实现忽略了write的返回值,实际应该处理写入错误情况

1、缓冲区空检查

cpp 复制代码
if(file->bufferlen <= 0) return;
  • 作用:如果缓冲区中没有数据,直接返回

  • 优化意义:避免不必要的系统调用

  • 潜在问题 :没有检查 file 是否为 NULL(健壮性考虑)

2、数据写入系统调用

cpp 复制代码
int n = write(file->fileno, file->outbuffer, file->bufferlen);
(void)n;  // 忽略返回值
  • write 系统调用

    • 参数1:文件描述符

    • 参数2:要写入的数据缓冲区

    • 参数3:要写入的字节数

    • 返回值:实际写入的字节数(可能小于请求的字节数)

  • 问题

    • 完全忽略了 write 的返回值,可能导致部分写入未被处理

    • 没有错误处理(如 write 返回 -1 表示出错)

3、数据同步到磁盘

cpp 复制代码
fsync(file->fileno);
  • fsync 系统调用

    • 强制将内核缓冲区中的数据写入物理磁盘

    • 确保数据持久化,不会因系统崩溃而丢失

  • 性能影响

    • 这是一个昂贵的操作,会显著降低 I/O 性能

    • 通常只在需要强一致性保证时使用

4、重置缓冲区

cpp 复制代码
file->bufferlen = 0;
  • 作用:将缓冲区长度重置为0,表示缓冲区已空

  • 实现方式

    • 只是简单地重置长度计数器

    • 没有清空缓冲区内容(可能的安全问题)


三、Makefile

bash 复制代码
# 生成动态链接库 libmyc.so,依赖 mystdio.o 和 mystring.o 两个目标文件
# $@ 表示目标文件(libmyc.so),$^ 表示所有依赖文件
libmyc.so: mystdio.o mystring.o
	gcc -shared -o $@ $^

# 编译 mystdio.c 生成位置无关代码(PIC)的目标文件
# -fPIC 选项是生成动态库所必需的
# $< 表示第一个依赖文件(mystdio.c)
mystdio.o: mystdio.c
	gcc -fPIC -c $<

# 编译 mystring.c 生成位置无关代码(PIC)的目标文件
mystring.o: mystring.c
	gcc -fPIC -c $<

# 伪目标:打包输出库文件
.PHONY: output
output:
	# 创建发布目录结构
	mkdir -p lib/include    # 存放头文件的目录
	mkdir -p lib/mylib      # 存放动态库的目录
	# 拷贝所有头文件到include目录
	cp -f *.h lib/include
	# 拷贝动态库文件到mylib目录
	cp -f *.so lib/mylib
	# 将整个lib目录打包压缩
	tar czf lib.tgz lib

# 伪目标:清理生成的文件
.PHONY: clean
clean:
	# 删除所有目标文件、动态库文件和生成的目录
	rm -rf *.o libmyc.so lib lib.tgz

四、Makefile-st

bash 复制代码
# 生成静态链接库 libmyc.a,依赖 mystdio.o 和 mystring.o 两个目标文件
# 注意:与动态库(.so)不同,静态库(.a)在编译时会被完整嵌入可执行文件
# $@ 表示目标文件(libmyc.a),$^ 表示所有依赖文件
libmyc.a: mystdio.o mystring.o
	# 使用 ar 命令打包目标文件生成静态库
	# -r 替换现有文件 -c 创建新库
	ar -rc $@ $^

# 编译 mystdio.c 生成目标文件
# 注意:静态库不需要 -fPIC 位置无关代码选项(与动态库不同)
# $< 表示第一个依赖文件(mystdio.c)
mystdio.o: mystdio.c
	gcc -c $<

# 编译 mystring.c 生成目标文件
mystring.o: mystring.c
	gcc -c $<

# 伪目标:打包发布静态库文件
.PHONY: output
output:
	# 创建标准的库发布目录结构
	mkdir -p lib/include    # 存放头文件(.h)的目录
	mkdir -p lib/mylib      # 存放静态库文件(.a)的目录
	# 拷贝所有头文件到include目录
	cp -f *.h lib/include
	# 拷贝静态库文件到mylib目录(注意这里是.a文件而非.so)
	cp -f *.a lib/mylib
	# 将整个lib目录打包压缩,便于分发
	tar czf lib.tgz lib

# 伪目标:清理生成的文件
.PHONY: clean
clean:
	# 删除编译过程中生成的所有中间文件
	# 包括:目标文件(.o)、静态库文件(.a)、打包目录和压缩包
	rm -rf *.o libmyc.a lib lib.tgz
相关推荐
Franciz小测测8 分钟前
proxmox 解决docker容器MongoDB创建报错MongoDB 5.0+ requires a CPU with AVX support
运维·docker·容器
果子⌂25 分钟前
Kubernetes 服务发布进阶
linux·运维·服务器·云原生·容器·kubernetes·云计算
SRC_BLUE_1727 分钟前
[网安工具] 自动化威胁检测工具 —— D 盾 · 使用手册
运维·自动化
望获linux38 分钟前
【Linux基础知识系列】第六十三篇 - 文件编辑器基础:vim
linux·运维·服务器·网络·嵌入式硬件·操作系统·嵌入式软件
书唐瑞1 小时前
Percona pt-archiver 出现长事务
java·服务器·数据库
极客奇点1 小时前
PowerShell自动化备份Windows事件日志实战指南
运维·ad·域控·自动化备份·活动目录系统
末日汐1 小时前
Linux常见指令
linux·运维·服务器
!chen2 小时前
Linux dd命令 数据备份、转换与磁盘操作的终极工具
linux·数据库·tomcat
rzl022 小时前
SpringBoot6-10(黑马)
linux·前端·javascript
weixin_548444262 小时前
《2025年5月鸽哒IM即时通讯原生双端APP源码解析:支持视频通话与实时语音(附实测数据)》
java·服务器·音视频