Linux 简单模拟实现C语言文件流

news2025/7/20 22:38:15

🌇前言

在 C语言 的文件流中,存在一个 FILE 结构体类型,其中包含了文件的诸多读写信息以及重要的文件描述符 fd,在此类型之上,诞生了 C语言 文件相关操作,如 fopenfclosefwrite 等,这些函数本质上都是对系统调用的封装,因此我们可以根据系统调用和缓冲区相关知识,模拟实现出一个简单的 C语言 文件流。

 注意: 本文实现的只是一个简单的 demo,重点在于理解系统调用及缓冲区

🏙️正文

1、FILE 结构设计

在设计 FILE 结构体前,首先要清楚 FILE 中有自己的缓冲区及冲刷方式

#pragma once
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<stdio.h>
#define flush_no 1   //无缓冲
#define flush_line 2  //行缓存
#define flush_all 4  //满了在缓冲
#define SIZE 1024

typedef struct IO_FILE
{
    int fileno;
    int _flush;
     
    char inbuffer[SIZE];
    char outbuffer[SIZE];
    int out_pos;
}_FILE;

void _fseek(_FILE*stream,int num,int f);

_FILE* _fopen(const char*filename,const char*flag);

int _fwrite (_FILE*fp,const char*s ,int len);

void _fclose(_FILE* fp);

void _fflush(_FILE* fp);

size_t _fread(void*ptr,size_t size,size_t num, _FILE*stream );

2、函数使用及分析

主要实现的函数有以下几个:

  • fopen 打开文件
  • fclose 关闭文件
  • fflush 进行缓冲区刷新
  • fwrite 对文件中写入数据
  • fread 读取文件数据

 实现的大致思路:

C语言文件流的过程。

FILE 结构体封装了用户级缓冲区(输入,输出)和文件指针,指向下一个读写的位置,还有文件标识符,等等。而fopen 是对open的封装
通过传入的读写方式打开对应的文件,在创建一个对应的结构体,初始化对应结构体的文件标识符,刷新方式。fwrite是对write的封装,它先是将要写入的数据写道FILE结构体封装的缓冲区里面,在判断是是否要刷新,如果要刷新就通过write写到对应的文件之中。fread封装的是read,它先是通过read将数据从文件中读到用户级缓冲区,然后在利用memcpy拷贝到目标数组中,这样可以减少拷贝次数。当fopen打开时采用“w+”的方式时,如果先向一个文件中写入一段数据,在调用fread读取的时候会发生问题,因为再写入时文件的指针在末尾,此时如果在去读的话,文件指针后面没有数据了,就会造成读取失败。在C语言中用户需要手动调用rewind() 或 fseek()函数将实现指针移动。fseek()是将lseek()函数进行封装,我们创建一个_fseek函数将系统接口 lseek()函数封装其中,将文件指针移动到文件的开始,也可以解决这个问题了。fclose在底层调用了close。在程序结束之前要对用户及缓冲区进行检查是否为空,不为空进行刷新。然后才可以将文件关闭。fflush函数封装的是write,它将缓冲区的数据写入文件中去。就这样我们可以实现一个简单的C语言文件流。实际上write写入应该写入到内核级缓冲区,但是我们无法实现这个,而且fflush可以对内核缓冲区进行刷新。

 代码:

#include"mystdio.h"

void _fseek(_FILE*stream,int num,int f)
{
   lseek(stream->fileno,num,f);
}


_FILE* _fopen(const char* filename,const char* flag)
{
   assert(filename&&flag);//不能传空串
   
   int fd=0;
   int f=0;//打开方式
   if(strcmp(flag,"r")==0)//读
   {
       f=O_RDONLY;//系统级打开的参数
       fd=open(filename,f,0666);
   }
   else if(strcmp(flag,"r+")==0)//读写
   {
       f=(O_RDONLY|O_WRONLY);
       fd=open(filename,f,0666);
   }

   else if(strcmp(flag,"w")==0)//两者相等返回0
   {
      f= (O_CREAT|O_WRONLY|O_TRUNC);
      fd=open(filename,f,0666);
   }
   else if(strcmp(flag,"w+")==0)//读写
   {
     //printf("w+\n");
     f=(O_RDWR | O_CREAT | O_TRUNC);
     fd=open(filename,f,0666);
   }

   else if(strcmp(flag,"a")==0)//追加写
   {
     f=(O_CREAT|O_WRONLY|O_APPEND);
     fd=open(filename,f,0666);
   }
   else if(strcmp(flag,"a+")==0)//追加读写
   {
   f=(O_CREAT|O_WRONLY|O_APPEND|O_RDONLY);                                                                         
   fd=open(filename,f,0666);  
   }

   else if(strcmp(flag,"r")==0)//只读
   {
     f=O_RDONLY;
     fd=open(filename,f);
   }
   else return NULL;

  if(fd==-1) return NULL;

  _FILE* fdd=(_FILE*)malloc(sizeof(_FILE));
  
  if(!fdd)//创建失败
  {
    perror("eror:\n");
    exit(-1);
  }

  memset(fdd, 0, sizeof(_FILE));//初始化用户及缓冲区
  fdd->fileno=fd; 
  //printf("fdd->fileno: %d\n", fdd->fileno)
  fdd->_flush=flush_line;
  fdd->out_pos=0;
  //printf("fdd->fileno: %d\n", fdd->fileno);
  return fdd ;
}

int _fwrite(_FILE*fp,const char* s,int len)
{
    memcpy(&fp->outbuffer[fp->out_pos],s,len);//先写到用户及缓冲区,再看刷新方式

    fp->out_pos+=len;
    
    if(fp->_flush==flush_no)//不缓存
    {
      write(fp->fileno,fp->outbuffer,fp->out_pos);
      fp->out_pos=0;
    }
    else if(fp->_flush==flush_line)//行缓存 abcd\n  5 
    {
        if(s[len-1]=='\n')
        {

         // printf("行\n");
          write(fp->fileno,fp->outbuffer,fp->out_pos);
          fp->out_pos=0;
        }
    }
    else{//全缓冲,满了才刷到文件中
        if(fp->out_pos==SIZE)
        {
          write(fp->fileno,fp->outbuffer,fp->out_pos);
          fp->out_pos=0;
        }
    } 

    return len;
}

void _fclose(_FILE* fp)
{
   assert(fp);
   
   if(fp->out_pos>0) _fflush(fp);//关闭之前先看用户及缓冲区

   close(fp->fileno); 
   free(fp);
}

void _fflush(_FILE* fp)//可以刷新内核级缓冲区
{
   if(fp->out_pos>0)
   {
     write(fp->fileno,fp->outbuffer,fp->out_pos);//刷新就是写入
     fp->out_pos=0;
     //lseek(fp->fileno, 0, SEEK_SET);
   }
}

size_t _fread(void*ptr,size_t size,size_t num, _FILE*stream)
{
   if(stream->out_pos>0) _fflush(stream);
    
   size_t readsize= size*num;//ptr的大小
   size_t buffersize=SIZE;//缓冲区的大小
   size_t total=0;

   if(buffersize>=readsize)
   { 
     ssize_t ret=read(stream->fileno,stream->inbuffer,readsize);
     
     if(ret<0)  
     {
       printf("不对\n");
       perror("ret\n");
     }
     
     memcpy(ptr,stream->inbuffer,ret);
     *((char*)ptr+ret)='\0';
     total=ret;

     //printf("%s\n",ptr);
   }
   else
   {
     while(total<readsize)
     {
       size_t ret=read(stream->fileno,stream->inbuffer,buffersize);
       if(ret<0) break;

       memcpy((char*)ptr+total,stream->inbuffer,ret);
       total+=ret;

       *((char*)ptr+total)='\0';
     } 
   }
  return total;
}

 main.c

#include "mystdio.h"
#include<stdio.h>
int main()
{
  _FILE* fp=_fopen("log.txt","w+");
  //printf("file: %d", fp->fileno);

  const char* str="hello,world\n";
 
  _fwrite(fp,str,strlen(str));
 
  _fseek(fp,0,SEEK_SET);
  

  char ptr[10]="";
  
  _fread(ptr,1,5,fp);

  printf("%s\n",ptr);
  
  _fclose(fp);

  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2396075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序使用npm包的方法

有用的链接 npm init -y 这个命令很重要, 会初始化 package.json 再重新打开微信小程序开发工具 选择工具中npm构建 在程序中引用时在main.js中直接使用包名的方式引用即可 如安装的是generator包&#xff0c;npm构建后就会生成 const myPackage require(***-generato…

Rust 学习笔记:发布一个 crate 到 crates.io

Rust 学习笔记&#xff1a;发布一个 crate 到 crates.io Rust 学习笔记&#xff1a;发布一个 crate 到 crates.io提供有用的文档注释常用标题文档注释作为测试注释所包含的项目 使用 pub use 导出一个方便的公共 API设置 crates.io 账户添加 metadata 到一个新的 crate发布到 c…

Vert.x学习笔记-EventLoop与Context的关系

Vert.x学习笔记 1. EventLoop 的核心作用2. Context 的核心作用3. EventLoop 与 Context 的关系1. 事件循环&#xff08;EventLoop&#xff09;的核心职责2. 上下文&#xff08;Context&#xff09;的核心职责3. 事件循环与上下文的关系&#xff08;1&#xff09;一对一绑定&am…

2025030给荣品PRO-RK3566开发板单独升级Android13的boot.img

./build.sh init ./build.sh -K ./build.sh kernel 【导入配置文件】 Z:\Android13.0\rockdev\Image-rk3566_t\config.cfg 【更新的内核】 Z:\Android13.0\rockdev\Image-rk3566_t\boot.img 【导入分区表&#xff0c;使用原始的config.cfg会出错的^_】 Z:\Android13.0\rockdev\…

由enctype-引出post与get的关系,最后深究至请求/响应报文

本篇载自我的笔记&#xff0c;本次为第二次复习。我觉得我有能力理一下思路了。 --- 笔记截图。 enctype HTML 表单的 enctype&#xff08;Encode Type&#xff0c;编码类型&#xff09;属性用于控制表单数据在提交到服务器时的编码方式&#xff0c;不同取值的详细解析如下&a…

搭建基于VsCode的ESP32的开发环境教程

一、VsCode搜索ESP-IDF插件 根据插件处搜索找到ESP-IDF并安装 安装完成 二、配置安装ESP-IDF 配置IDF 按照如下配置&#xff0c;点击安装 安装完成 三、使用案例程序 创建一个闪光灯的例子程序&#xff0c;演示程序编译下载。 选择blink例子&#xff0c;闪烁LED的程序 选…

【MFC】初识MFC

目录 01 模态和非模态对话框 02 静态文本 static text 01 模态和非模态对话框 首先我们需要知道模态对话框和非模态对话框的区别&#xff1a; 模态对话框是一种阻塞时对话框&#xff0c;它会阻止用户与应用程序的其他部分进行交互&#xff0c;直到用户与该对话框进行交互并关…

如何通过数据分析优化项目决策

通过数据分析优化项目决策需从明确数据分析目标、选择适当的数据分析工具、确保数据质量、建立数据驱动文化等方面入手&#xff0c;其中&#xff0c;明确数据分析目标是优化决策过程的基础&#xff0c;只有清晰明确的数据分析目标才能指导有效的数据采集与分析&#xff0c;避免…

2024年数维杯国际大学生数学建模挑战赛B题空间变量协同估计方法研究解题全过程论文及程序

2024年数维杯国际大学生数学建模挑战赛 B题 空间变量协同估计方法研究 原题再现&#xff1a; 在数理统计学中&#xff0c;简单采样通常假设来自相同总体的采样点彼此独立。与数理统计相反&#xff0c;空间统计假设空间变量的采样点是相依的&#xff0c;并在其值中表现出某些趋…

leetcode hot100刷题日记——34.将有序数组转换为二叉搜索树

First Blood&#xff1a;什么是平衡二叉搜索树&#xff1f; 二叉搜索树&#xff08;BST&#xff09;的性质 左小右大&#xff1a;每个节点的左子树中所有节点的值都小于该节点的值&#xff0c;右子树中所有节点的值都大于该节点的值。 子树也是BST&#xff1a;左子树和右子树也…

网页自动化部署(webhook方法)

实现步骤&#xff1a; 宝塔安装宝塔WebHook 2.5插件。 github 上配置网页仓库&#xff08;或可在服务器的网页根目录clone&#xff09;。 配置宝塔WebHook 2.5 添加hook脚本&#xff1b; 编辑添加syncJC脚本&#xff1b; #!/bin/bash # 定义网站根目录 WEBROOT"/www…

AU6825集成音频DSP的2x32W数字型ClaSSD音频功率放大器(替代TAS5825)

1.特性 ● 输出配置 - 立体声 2.0: 2 x 32W (8Ω,24V,THD N 10%) - 立体声 2.0: 2 x 26W (8Ω,21V,THD N 1%) ● 供电电压范围 - PVDD:4.5V -26.4V - DVDD: 1.8V 或者 3.3V ● 静态功耗 - 37mA at PVDD12V ● 音频性能指标 - THDN ≤ 0.02% at 1W,1kHz - SNR ≥ 107dB (A-wei…

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1商用服务体验全流程

华为云 Flexus 与 DeepSeek-V3/R1 的深度整合&#xff0c;构建了一套 “弹性算力 智能引擎” 的协同体系。 Flexus 系列云服务器基于柔性计算技术&#xff0c;通过动态资源调度&#xff08;如 Flexus X 实例&#xff09;实现 CPU / 内存的实时弹性分配&#xff0c;尤其适合大模…

C# NX二次开发-查找连续倒圆角面

在QQ群里有人问怎么通过一个选择一个倒圆角面来自动选中一组倒圆角面。 可以通过ufun函数 UF_MODL_ask_face_type 和 UF_MODL_ask_face_props 可判断处理选择相应的一组圆角面。 代码: Tag[] 查找连续倒圆角面(Tag faceTag) {theUf.Modl.AskFaceType(faceTag, out int typ…

今天遇到的bug

先呈现一下BUG现象。 这主要是一个传参问题&#xff0c;参数一直传不过去。后来我才发现&#xff0c;问题所在。 我们这里用的RquestBody接收参数&#xff0c;所有请求的参数需要用在body体中接收&#xff0c;但是我们用postman&#xff0c;用的是字符串查询方式传参&#x…

长安链智能合约命令解析(全集)

创建命令解析 ./cmc client contract user create \ --contract-namefact \ --runtime-typeWASMER \ --byte-code-path./testdata/claim-wasm-demo/rust-fact-2.0.0.wasm \ --version1.0 \ --sdk-conf-path./testdata/sdk_config.yml \ --admin-key-file-paths./testdata/cryp…

一、OpenCV的基本操作

目录 1、OpenCV的模块 2、OpenCV的基础操作 2.1图像的IO操作 2.2绘制几何图形 2.3获取并修改图像中的像素点 2.4 获取图像的属性 2.5图像通道的拆分与合并 2.6色彩空间的改变 3、OpenCV的算数操作 3.1图像的加法 3.2图像的混合 3.3总结 1、OpenCV的模块 2、OpenCV的基…

裂缝仪在线监测装置:工程安全领域的“实时守卫者”

在基础设施运维领域&#xff0c;裂缝扩展是威胁建筑结构安全的核心隐患之一。传统人工巡检方式存在效率低、时效性差、数据主观性强等局限&#xff0c;而裂缝仪在线监测装置通过技术迭代&#xff0c;实现了对结构裂缝的自动化、持续性追踪&#xff0c;为工程安全评估提供科学依…

【论文精读】2024 ECCV--MGLD-VSR现实世界视频超分辨率(RealWorld VSR)

文章目录 一、摘要二、问题三、Method3.1 Latent Diffusion Model3.2 Motion-guided Diffusion Sampling3.3 Temporal-aware Decoder Fine-tuning 四、实验设置4.1 训练阶段4.2 训练数据 贡献总结 论文全称&#xff1a; Motion-Guided Latent Diffusion for Temporally Consis…

SpringBoot简单体验

1 Helloworld 打开&#xff1a;https://start.spring.io/ 选择maven配置。增加SpringWeb的依赖。 Generate之后解压&#xff0c;代码大致如下&#xff1a; hpDESKTOP-430500P:~/springboot2/demo$ tree ├── HELP.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── s…