The C Programming Language(第 2 版) 笔记 / 8 UNIX 系统接口 / 8.5 实例(fopen 和 getc 函数的实现)

目录、参考文献


8.5 实例(fopengetc 函数的实现)

下面以标准库函数 fopengetc 的一种实现方法为例来说明如何将这些系统调用结合起来使用

标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的
文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:

  1. 一个指向缓冲区的指针,通过它可以一次读入文件的一大块内容
  2. 一个记录缓冲区中剩余的字符数的计数器
  3. 一个指向缓冲区中下一个字符的指针
  4. 文件描述符
  5. 描述读/写模式的标志
  6. 描述错误状态的标志
  7. 其它

描述文件的数据结构包含在头文件 <stdio.h>
任何需要使用标准输入/输出库中函数的程序都必须在源文件中包含这个头文件(通过 #include 指令包含头文件)
此文件也被库中的其它函数包含
在下面这段典型的 <stdio.h> 代码段中,只供标准库中其它函数所使用的名字以下划线开始,因此一般不会与用户程序中的名字冲突
所有的标准库函数都遵循该约定

#define NULL 0 
#define EOF (-1) 
#define BUFSIZ 1024 
#define OPEN_MAX 20 /* max #files open at once */

typedef struct _iobuf { 
    int cnt; /* characters left */ 
    char *ptr; /* next character position */ 
    char *base; /* location of buffer */ 
    int flag; /* mode of file access */ 
    int fd; /* file descriptor */ 
} FILE; 
extern FILE _iob[OPEN_MAX];

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

enum _flags { 
    _READ = 01, /* file open for reading */ 
    _WRITE = 02, /* file open for writing */ 
    _UNBUF = 04, /* file is unbuffered */ 
    _EOF = 010, /* EOF has occurred on this file */ 
    _ERR = 020 /* error occurred on this file */ 
};

int _fillbuf(FILE *); 
int _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 一般先将计数器减 1,将指针移到下一个位置,然后返回字符
一个长 #define 语句可用反斜杠分成几行
如果计数值变为负值,getc 就调用函数 _fillbuf 填充缓冲区,重新初始化结构的内容,并返回一个字符
返回的字符为 unsigned 类型,以确保所有的字符为正值

尽管在这里我们并不想讨论一些细节,但程序中还是给出了 putc 函数的定义,以表明它的操作与 getc 函数非常类似
当缓冲区满时,它将调用函数 _flushbuf
此外,我们还在其中包含了访问错误输出、文件结束状态和文件描述符的宏

fopen 函数的主要功能是打开文件,定位到合适的位置,设置标志位以指示相应的状态
它不分配任何缓冲区空间,缓冲区的分配是在第一次读文件时由函数 _fillbuf 完成的

#include <fcntl.h> 
#include "syscalls.h" 
#define PERMS 0666 /* RW for owner, group, others */

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; /* found free slot */ 
    if (fp >= _iob + OPEN_MAX) /* no free slots */
        return NULL; 
    if (*mode == 'w') 
        fd = creat(name, PERMS); 
    else if (*mode == 'a') { 
        if ((fd = open(name, O_WRONLY, 0)) == -1) 
            fd = creat(name, PERMS); 
        lseek(fd, 0L, 2); 
    } else 
        fd = open(name, O_RDONLY, 0); 
    if (fd == -1) /* couldn't access name */ 
        return NULL; 
    fp->fd = fd; 
    fp->cnt = 0; 
    fp->base = NULL; 
    fp->flag = (*mode == 'r') ? _READ : _WRITE; 
    return fp; 
}

该版本的 fopen 函数没有涉及标准 C 的所有访问模式,但是,加入这些模式并不需要增加多少代码
特别是,该版本的 fopen 不能识别表示二进制访问方式的 b 标志,这是因为,在 UNIX 系统中这种方式是没有意义的
同时,它也不能识别允许同时进行读和写的 + 标志

对于某一特定的文件,第一次调用 getc 函数时计数值为 0,这样就必须调用一次函数 _fillbuf
如果 _fillbuf 发现文件不是以读方式打开的,它将立即返回 EOF
否则,它将试图分配一个缓冲区(如果读操作是以缓冲方式进行的话)

建立缓冲区后,_fillbuf 调用 read 填充此缓冲区,设置计数值和指针,并返回缓冲区中的第一个字符
随后进行的 _fillbuf 调用会发现缓冲区已经分配

#include "syscalls.h"

/* _fillbuf: allocate and fill input buffer */ 
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) /* no buffer yet */ 
        if ((fp->base = (char *) malloc(bufsize)) == NULL) 
            return EOF; /* can't get buffer */ 
    fp->ptr = fp->base; 
    fp->cnt = read(fp->fd, fp->ptr, bufsize); 
    if (--fp->cnt < 0) { 
        if (fp->cnt == -1) 
            fp->flag |= _EOF; 
        else 
            fp->flag |= _ERR; 
        fp->cnt = 0; 
        return EOF; 
    } 
    return (unsigned char) *fp->ptr++; 
}

必须先定义和初始化数组 _iob 中的 stdinstdoutstderr 值:

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 执行缓冲方式的写操作


目录、参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值