Liunx下用C语言模拟实现 —— 封装文件操作接口

news2024/12/28 20:12:23

一、前提

本篇博客将循序渐进的模拟实现C语言中,简单版的对FILE和一些简单的文件操作接口的实现,在上一篇文件操作原理篇我们知道,实际的实现就是对系统接口的封装,为了巩固和练习之前学到的概念,我们也来简单的实现对接口的封装设计,要是对接口不太熟悉或者对原理还不太清楚,可以参考我上一篇的内容整理,链接:

http://t.csdnimg.cn/l5KbAicon-default.png?t=N7T8http://t.csdnimg.cn/l5KbA

二、代码设计

1.前提准备

先创建好一个工程的基本模版,采用声明和定义分离的方式,先创建好基本的文件

main.c 、 mystdio.c 、mysdtio.h 、 makefile

然后把makefile写好

myfile:main.c mystdio.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f myfile

2.头文件的设计

首先,我们的目的是自己封装一个FILE类型(简单版)去实现一些简单的文件操作c接口,所以先设计一下头文件中的声明

我们需要自己封装一个MY_FILE类型的结构体,里面至少要有fd,还有一个缓冲区,以及管理缓冲区内容的参数,还有就是缓冲区的刷新方式

然后就是相关的接口声明,这里简单实现三个基本接口,fopen、fwrite、fclose

最后是我们一定会用到的一个接口,fflush

#pragma once

#include<stdio.h>
//缓冲区大小
#define NUM 1024
//刷新策略
#define BUFF_NONE 0x01
#define BUFF_ROW 0x02
#define BUFF_ALL 0x04


typedef struct MY_FILE
{
  int _fd;
  char _buff[NUM];//缓冲区
  int _buffsize;
  int _flags;//刷新策略
}MY_FILE;

MY_FILE* my_fopen(const char* path,const char *mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemv,MY_FILE* stream);
int my_fclose(MY_FILE *fp);
int my_fflush(MY_FILE* fp);

3.接口实现

(1)my_fopen

我们本质要做的是对open函数的封装,因此在代码设计上,首先我们要比较我们一下fopen的参数和open的参数对比

fopen的使用是给路径和使用方式参数

open则是要给路径,并且带上标志位表示以哪种使用方式打开,然后是在创建新文件的时候要给到权限码

因此我们要做的就是根据fopen传的第二个参数,将其转换成open认识的参数形式,这里模拟实现简单一点,只实现三个最常见的“r”、“w”、“a”,我们用一个flags去根据不同情况去选择标志位,最后,再判断一下是否需要创建新的文件即可

MY_FILE* my_fopen(const char* path,const char* mode)
{
  int flag = 0;//标志位
  //根据不同情况记录标志位
  if(strcmp(mode,"r") == 0) flag |= O_RDONLY;
  else if(strcmp(mode,"w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);
  else if(strcmp(mode,"a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND );
  else{
    // other operator...
  }
  //默认权限码0666;
  mode_t m = 0666;
  //打开文件
  int fd = 0;
  if(flag & O_CREAT) fd = open(path,flag,m);
  else open(path,flag);
  //打开失败
  if(fd < 0) return NULL;
  //打开后需要设计返回值,FILE,初始化
  MY_FILE* ret = (MY_FILE*)malloc(sizeof(MY_FILE));
  if(ret == NULL)
  {
    close(fd);//这里如果malloc失败,记得关闭文件
    perror("malloc error");
    return NULL;
  }
  ret->_fd = fd;
  ret->_flags = BUFF_ROW;
  memset(ret->_buff,'\0',sizeof(ret->_buff));
  ret->_buffsize = 0;

  return ret;
}

(2)my_fclose

第二个实现的我们选择关闭文件,这样实现完成后可以选择局部先简单测试是否有问题

fclose函数主要做了四个事情:

1.清空缓冲区

这里我们单独实现一个my_fflush函数去完成,因为后续我们还会用到刷新缓冲区的操作、

2.关闭文件

close直接关闭即可

3.释放指针堆空间

free释放掉堆空间

4.将指针置空

fp = NULL;

int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1.清空缓冲区
  if(fp->_buffsize > 0) my_fflush(fp);
  //2.关闭文件
  close(fp->_fd);
  //3.释放堆空间
  free(fp);
  //4.指针置空
  fp = NULL;
  return 0;
}

(3)my_fwrite

首先我们先根据参数设计一下如何写代码

fwrite的参数设计是,第一个参数是数据所在的空间地址,第二个是数据块的大小,第三个是数据块的个数(这里可以简单理解,第二第三个参数相乘就是需要传入数据的字节数大小),第四个是传入的文件地址FILE

write的参数设计是第一个是文件描述符(fd),第二个是要传入数据的起始地址,第三个是数据字节大小

两者的参数设计类似,但是c语言中,fwrite得到的数据并不是直接通过write写入到文件中,而是会先到C语言自己封装的缓冲区中,根据不同文件而有不同的缓冲策略,但是这里我们简单模拟实现,默认行缓冲。

综上,代码设计的思路就是:

1.首先我们需要将得到的数据写入到缓冲区中,写入缓冲区时注意不要忘记调整相关的参数_buffsize(增删都要)

ps:我们简化了写入的策略,实际写入中,当数据超过缓冲区大小时,系统会自动帮我们清空缓冲区,并多次调用,直到将内容写完,不需要外部多次调用,这里我们做一个简化,我们规定,当数据量大于缓冲区剩余空间时,我们将其写满后停下,并且将写了多少字节的信息返回(实际返回的是成功写入的数据块个数)

2.检测当前的刷新策略,根据不同策略进行刷新

对应行刷新的策略,我们正常是需要检测这个字符内容中哪部分具有'\n'的,但这里也简单做一个简化,只检测倒数最后一个字符是否为“\n”

3.返回成功写入的字节数(简化调整)

//返回值简化一下,返回成功写入的字节数
size_t my_fwrite(const void *ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
  //先检查缓冲区是否已经满了,满了就刷新
  if(stream->_buffsize == NUM) my_fflush(stream);

  int my_size = size*nmemb;//用户要输入的数据大小
  int buff_capacity = NUM - stream->_buffsize;//缓冲区剩余的空间大小
  int ret = 0;

  if(buff_capacity >= my_size)
  {
    memcpy(stream->_buff + stream->_buffsize,ptr,my_size);
    stream->_buffsize += my_size;
    ret = my_size;
  }
  else
  {
    memcpy(stream->_buff + stream->_buffsize,ptr,buff_capacity);
    stream->_buffsize += buff_capacity;
    ret = buff_capacity;
  }

  //简化版的刷新策略
  if(stream->_flags & BUFF_ALL)
  {
    if(stream->_buffsize == NUM)
    {
      my_fflush(stream);
    }
  }
  else if(stream->_flags & BUFF_ROW)
  {
    if(stream->_buff[stream->_buffsize-1] == '\n')
    {
      my_fflush(stream);
    }
  }
  return ret;
}

(4)my_fflush

我们发现,实际真正写入到文件,是刷新缓冲区的时候,刷新缓冲区的步骤很简单,直接根据用write,将缓冲区的内容写入到文件中即可,稍微检测一下,当缓冲区里没内容时就不写,注意,写完后要调整管理缓冲区大小的参数_buffsize,将其置为0

int my_fflush(MY_FILE* fp)
{
  assert(fp);
  if(fp->_buffsize != 0)
  {
    write(fp->_fd,fp->_buff,fp->_buffsize);
  }
  fp->_buffsize = 0;
  return 0;
}

三、测试结果

其实,测试这个步骤是在写代码的过程中,每完成一个小模块就要去测试的,这里我们简单的用main函数去测试一下基本的文件读写功能,若是发现问题就慢慢调试找bug

四、参考代码

1.mystdio.h

#pragma once

#include<stdio.h>
//缓冲区大小
#define NUM 1024
//刷新策略
#define BUFF_NONE 0x01
#define BUFF_ROW 0x02
#define BUFF_ALL 0x04


typedef struct MY_FILE
{
  int _fd;
  char _buff[NUM];//缓冲区
  int _buffsize;
  int _flags;//刷新策略
}MY_FILE;

MY_FILE* my_fopen(const char* path,const char *mode);
size_t my_fwrite(const void* ptr,size_t size,size_t nmemv,MY_FILE* stream);
int my_fclose(MY_FILE *fp);
int my_fflush(MY_FILE* fp);

2.mystdio.c

#include"mystdio.h"
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<malloc.h>
#include<unistd.h>
#include<assert.h>

MY_FILE* my_fopen(const char* path,const char* mode)
{
  int flag = 0;
  if(strcmp(mode,"r") == 0) flag |= O_RDONLY;
  else if(strcmp(mode,"w") == 0) flag |= (O_CREAT | O_WRONLY | O_TRUNC);
  else if(strcmp(mode,"a") == 0) flag |= (O_CREAT | O_WRONLY | O_APPEND );
  else{
    // other operator...
  }
  mode_t m = 0666;
  int fd = 0;
  if(flag & O_CREAT) fd = open(path,flag,m);
  else open(path,flag);

  if(fd < 0) return NULL;

  MY_FILE* ret = (MY_FILE*)malloc(sizeof(MY_FILE));
  if(ret == NULL)
  {
    close(fd);
    perror("malloc error");
    return NULL;
  }
  ret->_fd = fd;
  ret->_flags = BUFF_ROW;
  memset(ret->_buff,'\0',sizeof(ret->_buff));
  ret->_buffsize = 0;

  return ret;
}

//返回值简化一下,返回成功写入的字节数
size_t my_fwrite(const void *ptr,size_t size,size_t nmemb,MY_FILE* stream)
{
  //先检查缓冲区是否已经满了,满了就刷新
  if(stream->_buffsize == NUM) my_fflush(stream);

  int my_size = size*nmemb;//用户要输入的数据大小
  int buff_capacity = NUM - stream->_buffsize;//缓冲区剩余的空间大小
  int ret = 0;

  if(buff_capacity >= my_size)
  {
    memcpy(stream->_buff + stream->_buffsize,ptr,my_size);
    stream->_buffsize += my_size;
    ret = my_size;
  }
  else
  {
    memcpy(stream->_buff + stream->_buffsize,ptr,buff_capacity);
    stream->_buffsize += buff_capacity;
    ret = buff_capacity;
  }

  //简化版的刷新策略
  if(stream->_flags & BUFF_ALL)
  {
    if(stream->_buffsize == NUM)
    {
      my_fflush(stream);
    }
  }
  else if(stream->_flags & BUFF_ROW)
  {
    if(stream->_buff[stream->_buffsize-1] == '\n')
    {
      my_fflush(stream);
    }
  }
  return ret;
}
  
int my_fflush(MY_FILE* fp)
{
  assert(fp);
  if(fp->_buffsize != 0)
  {
    write(fp->_fd,fp->_buff,fp->_buffsize);
  }
  fp->_buffsize = 0;
  return 0;
}

int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1.清空缓冲区
  if(fp->_buffsize > 0) my_fflush(fp);
  //2.关闭文件
  close(fp->_fd);
  //3.释放堆空间
  free(fp);
  //4.指针置空
  fp = NULL;
  return 0;
}

3.main.c(这个可以自己测试)

#include"mystdio.h"
#include<stdio.h>
#include<string.h>

int main()
{
  MY_FILE* fp = my_fopen("test.txt","w");
  if(fp == NULL)
  {
    perror("open error");
    return -1;
  }
  const char* s = "hello haha\n";
  my_fwrite(s,strlen(s),1,fp);
  my_fclose(fp);
  return 0;
}

4.makefile

myfile:main.c mystdio.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f myfile

总结

本次简单的模拟实现了一下FILE的封装和一些简单c库中文件操作接口的实现,加深对文件操作各个接口和系统调用的理解

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

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

相关文章

经典的免费wordpress模板

这款经典的免费WordPress模板以鲜艳的红色为主调&#xff0c;充满了活力与热情。设计简洁而现代&#xff0c;适合各种类型的项目网站。模板采用响应式设计&#xff0c;确保在不同设备和屏幕尺寸上都能呈现出完美的视觉效果。 红色象征着激情、活力和自信&#xff0c;这款模板…

【Python系列】受保护属性

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Redis分布式锁手动实现

Redis分布式锁手动实现 java中锁机制 在 Java 中&#xff0c;锁是用来同步并发访问共享资源的机制。它确保了在一个时间点&#xff0c;只有一个线程可以执行某个代码块或方法&#xff0c;从而防止了数据的不一致和竞态条件。Java 提供了多种锁机制&#xff0c;包括内置锁&…

springBoot集成seata实现分布式事务

背景 日常开发离不开分布式系统&#xff0c;自然避免不了分布式事务问题&#xff0c;seata 是一款阿里开源的主流分布式事务解决方案&#xff0c;但实际工作引入seata感觉有点重&#xff0c;本人之前在商业银行做开发对数据一致性要求很高&#xff0c;但很少团队使用。几年前曾…

PDF加密了无法编辑?解密方法来了!

一下午都在捣鼓各种格式问题&#xff0c;首先是需要合并几个 PDF&#xff0c;然而有一个文件加密了无法操作&#xff0c;碰到加密不能编辑就很头痛&#xff0c;终于让我找到一个可行的方法了&#xff0c; 首先就这个加密文件右键选择打开方式-Google Chrome>>打开>>…

K8s: 公有镜像中心和私有镜像中心的搭建

公有镜像中心的搭建和使用 1 &#xff09;在 官方docker镜像中心推送 在 hub.docker.com 上注册账号 (国内一般访问不了&#xff0c;原因不多说) 找到 Create Repository 按钮就行仓库的创建 这样就在官方创建了一个仓库&#xff0c;比如地址为: xx/y-y xx 是我的账户名y-y 是…

【服务器部署篇】Jenkins配置后端工程自动化部署

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0c;产…

计算机服务器中了mkp勒索病毒怎么办,mkp勒索病毒解密数据恢复流程

网络技术的不断应用与发展&#xff0c;为企业的生产运营带来了极大便利&#xff0c;越来越多的企业依赖网络开展各项工作业务&#xff0c;网络也大大提升了企业的生产运营效率&#xff0c;但网络是一把双刃剑&#xff0c;在为企业提供便利的同时&#xff0c;也为企业的数据安全…

IDEA生成测试类

方法一 具体流程: 选中要生成的测试类------------>选择code选项------------>选择Generate选项---------->选择test选项---------->选择要生成的方法 第一步: 光标选中需要生成测试类的类 找到code选项 选中Generate选项 选中test选项 选中你要生成的测试…

【目标检测】基于深度学习的酒瓶表面瑕疵缺陷检测(yolov5算法,6个类别,附代码和数据集)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。(专栏订阅用户订阅专栏后免费提供数据集和源码一份,超级VIP用户不在服务范围之内) 路虽远,行则将至;事虽难,做…

运行游戏提示dll文件丢失,分享多种有效的解决方法

在我们日常频繁地利用电脑进行娱乐活动&#xff0c;特别是畅玩各类精彩纷呈的电子游戏时&#xff0c;常常会遭遇一个令人困扰的问题。当我们满怀期待地双击图标启动心仪的游戏程序&#xff0c;准备全身心投入虚拟世界时&#xff0c;屏幕上却赫然弹出一条醒目的错误提示信息&…

springboot+vue生鲜超市销售管理系统

项目效果演示视频: springbootvue的生鲜商城的设计与实现 生鲜超市销售管理系统springbootvue 基于JAVAVueSpringBootMySQL的生鲜超市销售系统&#xff0c;包含了商品档案、商品进货、商品销售、供应商、活动管理、消息通知模块&#xff0c;还包含系统自带的用户管理、部门管理…

多输入多输出 | Matlab实现WOA-LSSVM鲸鱼算法优化最小二乘支持向量机多输入多输出预测

多输入多输出 | Matlab实现WOA-LSSVM鲸鱼算法优化最小二乘支持向量机多输入多输出预测 目录 多输入多输出 | Matlab实现WOA-LSSVM鲸鱼算法优化最小二乘支持向量机多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 Matlab实现WOA-LSSVM鲸鱼算法优化…

Linux系统----信号(万字文章超级详细并且简单易学附有实操shell指令图及注释!)

绪论​ “Do one thing at a time, and do well.”&#xff0c;本章开始Linux系统其中信号是学习操作系统的基本下面将会讲到什么是信号、信号的多种产生方式、信号如何保存的、信号如何处理的、以及一些信号的细节。话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑…

AS-V1000 视频监控平台,如何实现设备上线、下线时产生告警,及时通知管理人员

目录 一、客户需求 &#xff08;一&#xff09;客户需求 &#xff08;二&#xff09;掌握设备状况的意义 1、实时故障检测与预警 2、提升系统可靠性 3、优化资源配置 4、增强安全保障 5、提升管理效率 二、产品介绍 三、系统配置 &#xff08;一&#xff09;实现告警…

[网络安全] apt攻击是什么?

什么是APT攻击&#xff1a;APT攻击的主要特征包括&#xff1a;APT攻击的防御措施&#xff1a;零基础入门学习路线视频配套资料&国内外网安书籍、文档网络安全面试题 什么是APT攻击&#xff1a; APT&#xff08;Advanced Persistent Threat&#xff0c;高级持续性威胁&…

大小写不规范引起的LVS问题

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 往期文章链接: LVS常见问题解析 综合网表不规范,大小写混用常导致LVS问题,比如两个端口clk和CLK只有大小写区别,PR工具是可以识别为两个端口的,只不过Calibre LVS默认不区分大小写,会报错。 …

mac安装java

在 macOS 上配置 Java 环境变量是相对简单的。你需要做的是设置 JAVA_HOME 环境变量&#xff0c;并将 bin 目录添加到 PATH 变量中。本篇是最详细的教程&#xff0c;细化每个步骤过程&#xff0c;保姆级的教程&#xff01; 1. 下载JDK安装包 到oracle官网下载适合的JDK安装包…

外包干了4个月,技术退步明显

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01; 而我已经在一个企业干了四年的功能…

关于前后端一体项目SpringSecurity框架登陆失效,HTTPS重定向登陆页面异常的问题

现有环境是基于SpringBoot 2.6.8&#xff0c;然后是前后台一体化的项目。 安全框架使用的是内置版本的SpringSecurity。 场景&#xff1a;用户登陆&#xff0c;系统重启导致用户的session失效。但前端并没有跳转到对应的登录页&#xff0c;在HTTP的环境下可以正常跳转&#x…