C 标准I/O库的粗略实现教程分享!

写一下fopen/getc/putc等C库的粗略实现,参考了K&R,但是有几点根据自己理解的小改动,下面再具体说一下^_^

写这篇文章主要是帮助自己理解下标准I/O库大体是怎么工作的。

fopen与open之间的关系

操作系统提供的接口即为系统调用。而C语言为了让用户更加方便的编程,自己封装了一些函数,组成了C库。而且不同的操作系统对同一个功能提供的系统调用可能不同,在不同的操作系统上C库对用户屏蔽了这些不同,所谓一次编译处处运行。这里open为系统调用,fopen为C库提供的调用。

C 标准I/O库的粗略实现教程

C库对的读写操作封装了一个缓冲区。试想假如用户频繁的对文件读写少量字符,会频繁的进行系统调用(read函数),而系统调用比较耗时。C库自己封装了一个缓冲区,每次读取特定数据量到缓冲区,读取时优先从缓冲区读取,当缓冲区内容被读光后才进行系统调用将缓冲区再次填满。

C 标准I/O库的粗略实现教程

FILE结构体

上面我们看到一个结构体,里面有5个参数,分别记录了:缓冲区剩余的字符数cnt、下一个字符的位置ptr、缓冲区的位置base、文件访问模式flag、文件描述符fd。

其中文件描述符就是系统调用open返回的文件描述符fd,是int类型。ptr与base上面图中已经展示了。cnt是缓冲区剩余字符数,当cnt为0时,会系统调用read来填满缓冲区。flag为文件访问模式,记录了文件打开方式、是否到达文件结尾等。

结构体的具体定义如下,对应调用fopen返回的文件指针FILE *fp = fopen(xxx,r):

  typedef struct _iobuf{   int cnt; //缓冲区剩余字节数   char *base; //缓冲区地址   char *ptr; //缓冲区下一个字符地址   int fd; //文件描述符   int flag; //访问模式  } FILE; //别名,与标准库一致

结构体中有flag字段,flag字段可以是以下几种的并集:

  enum _flags {   _READ = 1,    _WRITE = 2,    _UNBUF = 4, //不进行缓冲   _EOF = 8,    _ERR = 16  };

我们注意到其中有一个字段,标识不进行缓冲,说明此种情况下每一次读取和输出都调用系统函数。一个例子就是标准错误流stderr : 当stderr连接的是终端设备时,写入一个字符就立即在终端设备显示。

而stdin和stdout都是带缓冲的,明确的说是行缓冲。本文不考虑行缓冲,默认都是全缓冲,即缓冲区满了才刷新缓冲区。(详细可以参考《UNIX环境高级编程》标准I/O库章节)。

现在我们可以初始化stdin、stdout与stderr:

  FILE _iob[OPEN_MAX] = {   {0,NULL,NULL,_READ,0},   {0,NULL,NULL,_WRITE,1},   {0,NULL,NULL,_WRITE|_UNBUF,2}  };

_ferror/_feof/_fileno

  //判断文件流中是否有错误发生  int _ferror(FILE *f){   return f-> flag & _ERR;  }  //判断文件流是否到达文件尾  int _feof(FILE *f){   return f-> flag & _EOF;  }  //返回文件句柄,即open函数的返回值  int _fileno(FILE *f){   return f->fd;  }

_fopen

  FILE *_fopen(char *file,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 == 0){    break;   }   }   if(fp >= _iob + OPEN_MAX){   return NULL;   }   if(*mode == 'w'){   fd = creat(file,PERMS);   }else if(*mode == 'r'){   fd = open(file,O_RDONLY,0);   }else{ //a模式   if((fd = open(file,O_WRONLY,0)) == -1){   fd = creat(file,PERMS);   }   lseek(fd,0L,2); //文件指针指向末尾   }   if(fd == -1){   return NULL;   }   fp->fd = fd;   fp->cnt = 0; //fopen不分配缓存空间   fp->base = NULL;   fp->ptr = NULL;   fp->flag = *mode == 'r' ? _READ : _WRITE;   return fp;  }

fopen的处理过程:

判断打开模式的合法性。

在_iob中寻找一个空闲位置,找不到的话说明程序打开的文件数已经到达的最大值,不能再打开新的文件。

如果是w模式,创建一个新文件。如果是r模式,以只读方式打开文件。如果是a模式,首先打开文件,如果打开失败则创建文件,否则通过系统调用lseek将文件指针置到末尾。

对FILE结构体进行初始化,注意fopen不会分配缓冲区。

_getc

getc的作用是从文件中返回下一个字符,参数是文件指针,即FILE:

  int _getc(FILE *f){   return --f->cnt >= 0 ? *f->ptr++ : _fillbuf(f);  }

对照上面的图示:当缓冲区中还有剩余字符待读取时,读取该字符并返回,并将缓冲区指针向后移动一个char单位,否则就调用_fillbuf函数填满缓冲区,_fillbuf的返回值就是待读取的字符。

这里有一个问题:当读取到最后一个字符时,cnt为0,但是ptr已经越界了,如下图:

C 标准I/O库的粗略实现教程

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/482046.html

(0)
上一篇 2020年11月9日
下一篇 2020年11月9日

精彩推荐