Пример - реализация функций FOPEN и GETC
Давайте теперь на примере реализации функций fopen и getc из стандартной библиотеки подпрограмм продемонстрируем, как некоторые из описанных элементов объединяются вместе.
Напомним, что в стандартной библиотеке файлы описываются посредством указателей файлов, а не дескрипторов. указатель файла является указателем на структуру, которая содержит несколько элементов информации о файле: указатель буфера, чтобы файл мог читаться большими порциями; счетчик числа символов, оставшихся в буфере; указатель следующей позиции символа в буфере; некоторые признаки, указывающие режим чтения или записи и т.д.; дескриптор файла.
Описывающая файл структура данных содержится в файле stdio.h, который должен включаться (посредством #include) в любой исходный файл, в котором используются функции из стандартной библиотеки. Он также включается функциями этой библиотеки. В приводимой ниже выдержке из файла stdio.h имена, предназначаемые только для использования функциями библиотеки, начинаются с подчеркивания, с тем чтобы уменьшить вероятность совпадения с именами в программе пользователя.
define _bufsize 512 define _nfile 20 /*files that can be handled*/ typedef struct _iobuf { char *_ptr; /*next character position*/ int _cnt; /*number of characters left*/ char *_base; /*location of buffer*/ int _flag; /*mode of file access*/ int _fd; /*file descriptor*/ } file; xtern file _iob[_nfile];
define stdin (&_iob[0]) define stdout (&_iob[1]) define stderr (&_iob[2])
define _READ 01 /* file open for reading */ define _WRITE 02 /* file open for writing */ define _UNBUF 04 /* file is unbuffered */ define _BIGBUF 010 /* big buffer allocated */ define _EOF 020 /* EOF has occurred on this file */ define _ERR 040 /* error has occurred on this file */ define NULL 0 define EOF (-1)
define getc(p) (--(p)->_cnt >= 0 \ ? *(p)->_ptr++ & 0377 : _filebuf(p)) define getchar() getc(stdin)
define putc(x,p) (--(p)->_cnt >= 0 \ ? *(p)->_ptr++ = (x) : _flushbuf((x),p)) define putchar(x) putc(x,stdout)
В нормальном состоянии макрос getc просто уменьшает счетчик, передвигает указатель и возвращает символ. (Если определение #define слишком длинное, то оно продолжается с помощью обратной косой черты). Если однако счетчик становится отрицательным, то getc вызывает функцию _filebuf, которая снова заполняет буфер, реинициализирует содержимое структуры и возвращает символ. функция может предоставлять переносимый интерфейс и в то же время содержать непереносимые конструкции: getc маскирует символ числом 0377, которое подавляет знаковое расширение, осуществляемое на PDP-11, и тем самым гарантирует положительность всех символов.
Хотя мы не собираемся обсуждать какие-либо детали, мы все же включили сюда определение макроса putc, для того чтобы показать, что она работает в основном точно также, как и getc, обращаясь при заполнении буфера к функции _flushbuf.
Теперь может быть написана функция fopen. Большая часть программы функции fopen связана с открыванием файла и расположением его в нужном месте, а также с установлением битов признаков таким образом, чтобы они указывали нужное состояние. функция fopen не выделяет какой-либо буферной памяти; это делается функцией _filebuf при первом чтении из файла.
#include <stdio.h> #define pmode 0644 /*r/w for owner;r for others*/ file *fopen(name,mode) /*open file,return file ptr*/ register char *name, *mode; { register int fd; register file *fp; if(*mode !='r'&&*mode !='w'&&*mode !='a') { fprintf(stderr,"illegal mode %s opening %s\n", mode,name); exit(1); } for (fp=_iob;fp<_iob+_nfile;fp++) if((fp->_flag & (_read | _write))==0) break; /*found free slot*/ if(fp>=_iob+_nfile) /*no free slots*/ return(null); if(*mode=='w') /*access file*/ fd=creat(name,pmode); else if(*mode=='a') { if((fd=open(name,1))==-1) fd=creat(name,pmode); lseek(fd,ol,2); } else fd=open(name,0); if(fd==-1) /*couldn't access name*/ return(null); fp->_fd=fd; fp->_cnt=0; fp->_base=null; fp->_flag &=(_read | _write); fp->_flag |=(*mode=='r') ? _read : _write; return(fp); }
функция _filebuf несколько более сложная. Основная трудность заключается в том, что _filebuf стремится разрешить доступ к файлу и в том случае, когда может не оказаться достаточно места в памяти для буферизации ввода или вывода. если пространство для нового буфера может быть получено обращением к функции calloc, то все отлично; если же нет, то _filebuf осуществляет небуферизованный ввод/вывод, используя отдельный символ, помещенный в локальном массиве.
#include <stdio.h> _fillbuf(fp) /*allocate and fill input buffer*/ register file *fp; ( static char smallbuf(nfile);/*for unbuffered 1/0*/ char *calloc(); if((fr->_flag& _read)==0 || (fp->_flag&(EOF|_err)) |=0 return(EOF); while(fp->_base==null) /*find buffer space*/ if(fp->_flag & _unbuf) /*unbuffered*/ fp->_base=&smallbuf[fp->_fd]; else if((fp->_base=calloc(_bufsize,1))==null) fp->_flag |=_unbuf; /*can't get big buf*/ else fp->_flag |=_bigbuf; /*got big one*/ fp->_ptr=fp->_base; fp->_cnt=read(fp->_fd, fp->_ptr, fp->_flag & _unbuf ? 1 : _bufsize); ff(--fp->_cnt<0) { if(fp->_cnt== -1) fp->_flag | = _EOF; else fp->_flag |= _ err; fp->_cnt = 0; return(EOF); } return(*fp->_ptr++ & 0377); /*make char positive*/ }
При первом обращении к getc для конкретного файла счетчик оказывается равным нулю, что приводит к обращению к _filebuf. Если функция _filebuf найдет, что этот файл не открыт для чтения, она немедленно возвращает EOF. В противном случае она пытается выделить большой буфер, а если ей это не удается, то буфер из одного символа. При этом она заносит в _flag соответствующую информацию о буферизации.
Раз буфер уже создан, функция _filebuf просто вызывает функцию read для его заполнения, устанавливает счетчик и указатели и возвращает символ из начала буфера.
Единственный оставшийся невыясненным вопрос состоит в том, как все начинается. Массив _iob должен быть определен и инициализирован для stdin, stdout и stderr:
file _iob[nfile] = { (null,0,_read,0), /*stdin*/ (null,0,null,1), /*stdout*/ (null,0,null,_write | _unbuf,2) /*stderr*/ };