C语言KR圣经笔记 8.5样例 - fopen和getc的实现

8.5 样例 - fopen 和 getc 的实现

通过给出标准库例程 fopen 和 getc 的一个实现,我们来说明如何将前面这些内容组合起来。

回忆一下,在标准库中,文件用文件指针而不是文件描述符来描述。文件指针是包含一些文件信息的结构体指针:缓存指针,有缓存的话文件就能以大块的方式读入;缓存区剩下的字符个数;指向缓存区下一个字符的指针;文件描述符;表示读写模式、错误状态等信息的标志位。

用于描述文件的数据结构包含在 <stdio.h> 中,任何需要使用标准输入/输出库例程的源文件,都必须通过 #include 包含它。标准库中的很多函数也会包含它。下面的代码片段来自一个典型的 <stdio.h> 头文件,其中只打算被库函数使用的名称以下划线开头,这样它们与用户程序中名称冲突的可能性就更小。所有标准库的例程都采用这个约定。

#define NULL        0
#define EOF         (-1)
#define BUFSIZE     1024
#define OPENMAX     20  /* 一次能打开的最多文件数 */typedef struct _iobuf {int cnt;        /* 剩余的字符数 */char *ptr;      /* 下一个字符位置 */char *base;     /* 缓存区的位置 */int flag;       /* 文件访问模式 */int fd;         /* 文件描述符 */
} FILE;
extern FILE _iob[OPEN_MAX];#define stdin    (&_iob[0])
#define stdout   (&_iob[1])
#define stderr   (&_iob[2])enum _flags {_READ  = 01,    /* 打开文件用于读 */_WRITE = 02,    /* 打开文件用于写 */_UNBUF = 04,    /* 文件无缓存 */_EOF   = 010,   /* 文件发生了EOF */_ERR   = 020    /* 文件发生了错误 */
};int _filbuf(FILE *);
inf _flushbuf(int, FILE *);#define feof(p)    (((p)->flag & _EOF) != 0)
#define ferror(p)  (((p)->flag & _ERR) != 0)
#define fileno(p)  ((p)->fd)#define getc(p)    (--(p)->cnt >= 0 \? (unsigned char) *(p)->ptr++ : _fillbuf(p))
#define putc(x,p)  (--(p)->cnt >=0 \?  *(p)->ptr++ = (x) : _flushbuf((x), p))#define getchar()    getc(stdin)
#define putchar(x)   putc((x), stdout)


 
getc 宏通常是对字符数量递减,向前移动指针,并返回字符。(回忆一下,长的 #define 通过反斜杠来续行。)然而,如果数量变为负,则 getc 调用 _fillbuf 来重新填充缓存,重新初始化结构体的内容,并返回一个字符。字符返回成 unsigned,以保证所有字符都是正数。


尽管并不准备讨论任何细节,我们还是把 putc 的定义包含进来,以说明它和 getc 的工作方式大致相同,在缓存满时调用 _flushbuf 函数。代码里还包含了获取错误状态和文件结束状态,以及文件描述符的宏。

现在可以写 fopen 函数了。open 函数主要关注的是:打开文件,定位到正确的位置,设置标志位以表示正确的状态。fopen 不分配任何缓存空间;缓存是文件首次打开时由 _fillbuf 来做的。

#include <fcntl.h>
#include "syscalls.h"
#define PERMS    0666    /* 文件属主、组和其他用户都可读写 *//* fopen: 打开文件,返回文件指针 */
FILE *fopen(char *name, char *mode)
{int fd;FILE *fp;if (*mode != 'r' && *mode != 'w' && *mode != 'a')return NULL;for (fp = _iob; fp < _iob + OPEN_MAX; fp++)if ((fp->flag & (_READ | _WRITE)) == 0)break;    /* 找到了空闲槽位 */if (fp >= _iob + OPEN_MAX)    /* 没有空闲槽位 */return NULL;if (*mode == 'w')fd = creat(name, PERMS);else (*mode == 'a') {if ((fd = open(name, O_WRONLY, 0)) == -1)fd = creat(name, PERMS);lseek(fd, 0L, 2);} elsefd = open(name, O_RDONLY, 0);if (fd == -1)    /* 无法访问 */return NULL;fp->fd = fd;fp->cnt = 0;fp->base = NULL;fp->flag = (*mode == 'r') ? _READ : _WRITE;return fp;
}

这个版本的 fopen 没有处理标准中所有可能的模式,不过要加上也不用太多代码。特别指出,这个版本的 fopen 不认识表示二进制模式的 “b”,因为在 UNIX系统上没有意义,也不认识表示同时允许读和写的 “+” 号。

对某个文件的首次 getc 调用会发现数量为 0,这就使 _fillbuf 被强制调用。如果 _fillbuf 发现文件没有为读而打开,它马上返回 EOF。否则,它尝试分配一个缓存(如果读需要缓存)。

一旦缓存建立起来,_fillbuf 会调用 read 来填充缓存,设置数量和指针,并返回缓存头部的字符。后续调用 _fillbuf 时会发现缓存已经分配好了。

#include "syscalls.h"/* _filbuf: 分配并填充输入缓存 */
int _fillbuf(FILE *fp)
{int bufsize;if ((fp->flag & (_READ|_EOF|ERR)) != _READ)return EOF;bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZ;if (fp->base == NULL)     /* 还没有缓存 */if ((fp->base = (char *)malloc(bufsize)) == NULL)return EOF;       /* 无法获取缓存 */fp->ptr = fp->base;fp->cnt = read(fp->fd, fp->ptr, bufsize);if (--fp->cnt < 0) {if (fp->cnt == -1)fp->flag |= _EOF;elsefp->flag |= _ERR;fp->cnt = 0;return EOF;}return (unsigned char) *fp->ptr++;
}

剩下还没说的是从哪里开始。必须定义 _iob 数组,并初始化 stdin,stdout 和 stderr 。

FILE _iob[OPEN_MAX] = {    /* stdin, stdout, stderr: */{ 0, (char *)0, (char *)0, _READ, 0 },{ 0, (char *)0, (char *)0, _WRITE, 1 },{ 0, (char *)0, (char *)0, _WRITE |_UNBUF, 2 }
};


结构体中 flag 成员的初始化代码表明了 stdin 用来读,stdout 用来写,而 stderr 是无缓冲写。

练习8-2、重写 fopen 和 _fillbuf,使用位域而不是显式的比特位操作。比较代码量和执行速度的差异。

练习8-3、设计并实现 _flushbuf、fflush 和 fclose。

练习8-4、实现标准库函数 fseek

int fseek(FILE *fp, long offset, int origin)

它与 lseek 相同,区别仅在于 fp 是文件指针而不是文件描述符,且返回值是 int 而不是位置。保证你写的 fseek 与标准库中其他函数的缓存能一起协调配合。

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

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

相关文章

金融贷款风险预测:使用图神经网络模型进行违约概率评估

要使用PyTorch和GNN&#xff08;图神经网络&#xff09;来预测金融贷款风险&#xff0c;并加入注意力机制&#xff0c;我们首先需要构建一个贷款风险预测的图数据集。然后&#xff0c;我们将设计一个基于注意力机制的GNN模型。 以下是一个简化的代码示例&#xff0c;演示了如何…

前端框架的CSS模块化(CSS Modules)

创作纪念日之际&#xff0c;来给大家分享一篇文章吧 聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们…

Vue前端对请假模块——请假开始时间和请假结束时间的校验处理

开发背景&#xff1a;Vueelement组件开发 业务需求&#xff1a;用户提交请假申请单&#xff0c;请假申请的业务逻辑处理 实现&#xff1a;用户选择开始时间需要大于本地时间&#xff0c;不得大于请假结束时间&#xff0c;请假时长根据每日工作时间实现累加计算 页面布局 在前…

Linux socket函数

什么是协议 在网络编程中&#xff0c;协议是指计算机系统之间进行通信和交互所遵循的规则和约定。它定义了数据的格式、传输方式、错误处理、认证和授权等方面的规范&#xff0c;以确保不同计算机之间能够正确地交换信息。 协议分为多个层次&#xff0c;每个层次负责不同的功…

二进制部署k8s之网络部分

1 CNI 网络组件 1.1 K8S的三种接口 CRI 容器运行时接口 docker containerd podman cri-o CNI 容器网络接口 flannel calico cilium CSI 容器存储接口 nfs ceph gfs oss s3 minio 1.2 K8S的三种网络 节点网络 nodeIP 物理网卡的IP实现节点间的通信 Pod网络 podIP Pod与Po…

视频和音频使用ffmpeg进行合并和分离(MP4)

1.下载ffmpeg 官网地址&#xff1a;https://ffmpeg.org/download.html 2.配置环境变量 此电脑右键点击 属性 - 高级系统配置 -高级 -环境变量 - 系统变量 path 新增 文件的bin路径 3.验证配置成功 ffmpeg -version 返回版本信息说明配置成功4.执行合并 ffmpeg -i 武家坡20…

GOOGLE Colab Pro会员订阅开通购买付费充值教程

一、简介 colab由谷歌团队开发&#xff0c;用于机器学习、数据分析&#xff0c;教育等目的&#xff0c;他的会员也非常的昂贵&#xff0c;最基本的套餐要10美金&#xff0c;最高要50美金一个月&#xff0c;如何省钱&#xff0c;往下看。 一般来说土区的价格比较便宜&#xff0…

Day02:Web架构前后端分离站Docker容器站集成软件站建站分配

目录 常规化站点部署 站库分离 前后端分离 集成软件搭建Web应用 Docker容器搭建Web应用 建立分配站 静态 与 伪静态 总结 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗…

基于单片机的企业指纹考勤系统设计

摘要: 考勤系统是企业人力资源管理的重要依据,传统的考勤系统不能保证准确性,也存在地域局限,不能满足一些跨区域集团公司的考勤要求。文章以单片机技术以及生物特征识别技术为基础,分析企业单片机智能化指纹考勤系统的设计思路,从硬件设备的选型和配置、软件系统的开发、…

ES6 对象面试题

ES6 对象面试题 问题描述&#xff1a; 什么是对象的简洁表示法&#xff08;Shorthand Property&#xff09;&#xff1f;给出一个使用简洁表示法的示例。 答案&#xff1a; 对象的简洁表示法是一种在对象字面量中定义属性的简化语法。当属性名和变量名相同时&#xff0c;可以省…

怎么把pdf转换成word?

怎么把pdf转换成word&#xff1f;Pdf和word在电脑上的使用非常广泛&#xff0c;pdf和word分别是由 Adobe和Microsoft 分别开发的电脑文件格式。PDF 文件可以在不同操作系统和设备上保持一致的显示效果&#xff0c;无论是在 Windows、Mac 还是移动设备上查看&#xff0c;都能保持…

【学习笔记】SCI投稿状态

大多数SCI期刊都有自己的在线投稿系统&#xff0c;如ScholarOne Manuscripts、Editorial Manager等&#xff0c;这些系统使得投稿、审稿、编辑和出版过程变得更加高效和透明。投稿系统通常要求作者注册账户&#xff0c;然后根据指导逐步提交稿件及相关信息。 投稿状态介绍 Sub…

Encoder和Decoder的详细介绍

——参考傅里叶变换&#xff08;Encoder&#xff09;、处理和傅里叶逆变换&#xff08;Decoder&#xff09; 1.Encoder&#xff08;编码器&#xff09; 编码器&#xff08;Encoder&#xff09;通常指的是在机器学习和神经网络领域中的一种组件或模型结构&#xff0c;用于将输入…

配置多个后端 API 代理

在开发 React 应用时&#xff0c;通常会涉及到与后端 API 的交互。而在开发过程中&#xff0c;我们经常需要在开发环境中使用代理来解决跨域请求的问题。Create React App 提供了一种简单的方式来配置代理&#xff0c;即通过创建一个名为 setupProxy.js 的文件来配置代理规则。…

《数据治理简易速速上手小册》第4章 数据安全与合规性(2024 最新版)

文章目录 4.1 数据安全的基本原则4.1.1 基础知识4.1.2 重点案例&#xff1a;在线零售商的数据加密4.1.3 拓展案例 1&#xff1a;医疗机构的访问控制4.1.4 拓展案例 2&#xff1a;金融服务提供商的数据备份和恢复 4.2 遵循数据合规性的策略4.2.1 基础知识4.2.2 重点案例&#xf…

PHPStudy无法解析php(7.3.4)文件

#告诉服务器&#xff0c;对于以.fcgi、.php或.phtml为后缀的请求&#xff0c;应该使用FPM进行处理。 AddHandler fcgid-script .fcgi .php .phtml #设置了全局默认使用的PHP版本路径 FcgidInitialEnv PHPRC "D:/phpstudy_pro/Extensions/php/php7.3.4nts" #告诉服务器…

Java制作比对数据的工具

要制作一个Java工具来比对数据&#xff0c;你需要考虑几个关键点&#xff1a;数据的来源、数据的格式、比对算法和结果输出。以下是一个简单的步骤&#xff0c;帮助你开始制作这样的工具&#xff1a; 确定数据来源&#xff1a; 数据可能来自文件、数据库、API或其他来源。根据数…

Django学习笔记-ModelForm使用(完全依赖)

1.创建模型 ,code,name,sex,entrydate 2.模型映射 python manage.py makemigrations myapp01,python manage.py migrate 3.创建模型表单,继承forms.ModelForm,Meta:元数据,models需引入,fields填写引用的模型变量 4.创建testModelForm.html,添加urls 5.views编写testmodelfo…

simple-pytest 框架使用指南

simple-pytest 框架使用指南 一、框架介绍简介框架理念&#xff1a;框架地址 二、实现功能三、目录结构四、依赖库五、启动方式六、使用教程1、快速开始1.1、创建用例&#xff1a;1.2、生成py文件1.3、运行脚本1.3.1 单个脚本运行1.3.2 全部运行 1.4 报告查看 2、功能介绍2.1、…

小程序开发之配置服务器域名

在小程序开发中&#xff0c;配置服务器域名是为了允许小程序能够从指定的后端服务器获取数据或进行通信。这通常涉及以下步骤&#xff1a; 1. **登录微信公众平台**&#xff1a;首先&#xff0c;需要登录微信公众平台&#xff0c;并找到你的小程序管理页面。 2. **进入开发设…