目录、参考文献
8.5 实例(fopen 和 getc 函数的实现)
下面以标准库函数 fopen 和 getc 的一种实现方法为例来说明如何将这些系统调用结合起来使用
标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的
文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:
- 一个指向缓冲区的指针,通过它可以一次读入文件的一大块内容
- 一个记录缓冲区中剩余的字符数的计数器
- 一个指向缓冲区中下一个字符的指针
- 文件描述符
- 描述读/写模式的标志
- 描述错误状态的标志
- 其它
描述文件的数据结构包含在头文件
任何需要使用标准输入/输出库中函数的程序都必须在源文件中包含这个头文件(通过 #include 指令包含头文件)
此文件也被库中的其它函数包含
在下面这段典型的
所有的标准库函数都遵循该约定
#define NULL 0
#define EOF (-1)
#define BUFSIZ 1024
#define OPEN_MAX 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,
_ERR = 020
};
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#include "syscalls.h" #define PERMS 0666 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 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) 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"
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;
else
fp->flag |= _ERR;
fp->cnt = 0;
return EOF;
}
return (unsigned char) *fp->ptr++;
}
必须先定义和初始化数组 _iob 中的 stdin、stdout 和 stderr 值:
FILE _iob[OPEN_MAX] = {
{ 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 执行缓冲方式的写操作
目录、参考文献



