设为首页 加入收藏

TOP

C 标准I/O库粗略实现(三)
2018-10-21 18:11:01 】 浏览:256
Tags:标准 I/O 粗略 实现
p; _ERR判断是否出错。

注意,这里我们只针对可写的文件流进行操作,忽略了只读的文件流:

If the stream was open for reading, the behavior depends on the specific implementation. In some implementations this causes the input buffer to be cleared.

针对只读的文件流,不同系统处理的方式不一样,有的系统会清空缓冲区。

_fclose

int _fclose(FILE *f){
    int ret;
    if((ret = _fflush(f)) != EOF){
        free(f->base);
        f->base = NULL;
        f->ptr = NULL;
        f->fd = 0;
        f->flag = 0;        
                f->cnt=0;
    }

    return 0;
}

fclose调用fflush函数,保证在文件关闭前将缓冲区中的内容刷到文件中,并且释放掉缓冲区的内存空间。

_fseek
关于fseek的介绍请看fseek

int _fseek(FILE *f,long offset,int origin){

    int rc;

    if(f->flag & _READ) {
        if(origin == 1) {
            offset -= f->cnt;
        }
        rc = lseek(f->fd,offset,origin);
                f->cnt = 0;             //将缓冲区剩余字符数清0
    }else if(f->flag & _WRITE) {
                rc = _fflush(f);       //强制刷新缓冲区
        if(rc != EOF) {
            rc = lseek(f->fd,offset,origin);
        }
    }

    return rc == -1 ? EOF : 0;
}

当文件流为可读时,见下图:
image

由于有缓冲区的存在,我们直觉上的文件指针位置和真实的文件指针位置是不同的,差了cnt个单位长度。所以当我们设置移动offset个长度时,真实的文件指针需要移动offset-cnt个单位长度(offset为正数或者负数)。
之后我们需要将cnt置为0,以便下次读取时将缓冲区的数据更新。
当origin为0或者2时,直接调动lseek即可。

而当文件流为可写时,见下图:
image

真实的文件指针位置与我们直觉上的文件指针位置差了ptr - base个单位长度,即我们新写入缓冲区的内容长度,所以我们直接调用_fflush即可。(K&R中直接调用的write,但是我觉得这样没有重置ptr指针的位置和cnt,这样的话base与ptr之间的内容会被刷入到文件中两次)。

当文件是以a模式打开时,fseek无效:

a+     Open for reading and appending (writing at end of file).  The file is created if it does not exist.  The initial file 
       position for  reading is at the beginning of the file, but output is always appended to the end of the file.

_getchar

int _getchar(){
  return _getc(stdin);
}

我们可以发现,_getchar调用的就是_getc,只不过_getc可以传入任意的文件指针,而对_getchar来说,_getc传入的是stdin,也就是{0,NULL,NULL,_READ,0}

image

  • 当调用getchar时,首先去stdin结构体中的缓存取数据,如果缓存为空,会在_fillbuf中的int n = read(f->fd,f->ptr,BUFSIZ); //系统调用read处阻塞住,等待用户输入字符。
  • 当标准输入(stdin)连接的是终端时,终端I/O会采用规范模式输入处理:对于终端输入以行为单位进行处理,对于每个读请求,终端设备输入队列会返回一行(用户输入的字符会缓存在终端输入队列中,直到用户输入一个行定界符,输入队列中的数据会返回给read函数)。
  • 这一行数据会缓存在标准I/O缓冲区中,下次调用getchar时会返回缓冲区第一个字符。当缓冲区数据被读光时,重复上述过程。

_putchar

int _putchar(int x){
    return _putc(x,stdout);
}

我们可以发现,_putchar调用的就是_putc,只不过_putc可以传入任意的文件指针,而对_putchar来说,_putc传入的是stdout,也就是{0,NULL,NULL,_WRITE,1}

image

  • 调用putchar时,数据会缓存在stdout中的缓冲中。
  • 当stdout的缓冲被装满时,会调用write将数据写入到stdout中,stdout将数据写入到终端设备输出队列中,输出队列将数据写入到终端。

完整代码

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define EOF -1
#define BUFSIZ 1024
#define OPEN_MAX 20      //打开的最大文件数
#define PERMS 0666

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

extern FILE _iob[OPEN_MAX];

//八进制
enum _flags {
    _READ = 01,     
    _WRITE = 02,    
    _UNBUF = 04,     //不进行缓冲
    _EOF = 010,     
    _ERR = 020  
};

FILE _iob[OPEN_MAX] = {
        {0,NULL,NULL,_READ,STDIN_FILENO},
        {0,NULL,NULL,_WRITE,STDOUT_FILENO},
        {0,NULL,NULL,_WRITE|_UNBUF,STDERR_FILENO}
};

#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])

int _ferror(FILE *f){
    return f-> flag & _ERR;
}

int _feof(FILE *f){
    return f-> flag & _EOF;
}

int _fileno(FILE *f){
    return f->fd;
}

//返回第一个字符
int _fillbuf(FILE *f){

    int bufsize;

    if((f->flag & (_READ | _EOF | _ERR)) != _READ){     //判断文件是否可读
        return EOF;
    }

    bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;

    if(f->base == NULL){            //没有分配过缓冲区
        if((f->base = (char *)malloc(bufsize)) == NU
首页 上一页 1 2 3 4 下一页 尾页 3/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇十六进制带小数转换成十进制 下一篇关于C语言中static保留字的使用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目