Керниган и Ритчи - Язык программирования Си (793773), страница 42
Текст из файла (страница 42)
Реализация функций fopen и getcТеперь на примере функций fopen и getc из стандартной библиотеки покажем, как описанные выше частисогласуются друг с другом.Напомним, что файлы в стандартной библиотеке описываются файловыми указателями, а не дескрипторами.Указатель файла — это указатель на структуру, содержащую информацию о файле: указатель на буфер,позволяющий читать файл большими кусками; число незанятых байтов буфера; указатель на следующуюпозицию в буфере; дескриптор файла; флажки, описывающие режим (чтение/запись), ошибочные состоянияи т. д.Структура данных, описывающая файл, содержится в <stdio.h>, который необходимо включать (спомощью #include) в любой исходный файл, если в том осуществляется стандартный ввод-вывод. Этот жезаголовочный файл включен и в исходные тексты библиотеки ввода-вывода.В следующем фрагменте, типичном для файла <stdio.h>, имена, используемые только в библиотечныхфункциях, начинаются с подчеркивания.
Это сделано для того, чтобы они случайно не совпали с именами,фигурирующими в программе пользователя. Такое соглашение соблюдается во всех программах стандартнойбиблиотеки.#define#define#define#defineNULL ОEOF (-1)BUFSIZ 1024OPEN_MAX 20 /* max число одновременно открытых файлов */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, /* в данном файле встретился EOF */_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 обычно уменьшает счетчик числа символов, находящихся в буфере, и возвращает символ,после чего приращивает указатель на единицу.
(Напомним, что длинные #define с помощью обратнойнаклонной черты можно продолжить на следующих строках.) Когда значение счетчика становитсяотрицательным, getc вызывает _fillbuf, чтобы снова заполнить буфер, инициализировать содержимоеструктуры и выдать символ.
Типы возвращаемых символов приводятся к unsigned; это гарантирует, что всеони будут положительными.Хотя в деталях ввод-вывод здесь не рассматривается, мы все же привели полное определение putc. Сделаноэто, чтобы показать, что она действует во многом так же, как и getc, вызывая функцию _flushbuf, когдабуфер полон. В тексте имеются макросы, позволяющие получать доступ к флажкам ошибки и конца файла, атакже к его дескриптору.Теперь можно написать функцию fopen. Большая часть инструкций fopen относится к открытию файла, ксоответствующему его позиционированию и к установке флажковых битов, предназначенных для индикациитекущего состояния.
Сама fopen не отводит места для буфера; это делает _fillbuf при первом чтениифайла.#include <fcntl.h>#include "syscalls.h"#define PERMS 0666 /* RW для собственника, группы и проч. *//* fopen: открывает файл, возвращает файловый указатель */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);} elsefd = open(name, O_RDONLY, 0);if (fd == -1) /* невозможен доступ по имени name */return NULL;fp->fd = fd;fp->cnt = 0;fp->base = NULL;fp->flag = (*mode == 'r' ) ? _READ : _WRITE;return fp;}Приведенная здесь версия fopen реализует не все режимы доступа, оговоренные стандартом; но, мыдумаем, их реализация в полном объеме ненамного увеличит длину программы.
Наша fopen не распознаетбуквы b, сигнализирующей о бинарном вводе-выводе (поскольку в системах UNIX это не имеет смысла), изнака +, указывающего на возможность одновременно читать и писать.Для любого файла в момент первого обращения к нему с помощью макровызова getc счетчик cnt равеннулю. Следствием этого будет вызов _fillbuf. Если выяснится, что файл на чтение не открыт, то функция_fillbuf немедленно возвратит EOF. В противном случае она попытается запросить память для буфера(если чтение должно быть с буферизацией).После получения области памяти для буфера _fillbuf обращается к read, чтобы его наполнить,устанавливает счетчик и указатели и возвращает первый символ из буфера. В следующих обращениях_fillbuf обнаружит, что память для буфера уже выделена.#include "syscalls.h"/* _fillbuf: запрос памяти и заполнение буфера */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;elsefp->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, (char *) 0, (char{ 0, (char *) 0, (char};stdin, stdout, stderr: */*) 0, _READ, 0 },*) 0, _WRITE, 1 },*) 0, _WRITE | _UNNBUF, 2 }Инициализация flag как части структуры показывает, что stdin открыт на чтение, stdout — на запись, astderr — на запись без буферизации.Упражнение 8.2. Перепишите функции fopen и _fillbuf, работая с флажками как с полями, а не спомощью явных побитовых операций. Сравните размеры и скорости двух вариантов программ.Упражнение 8.3.
Разработайте и напишите функции _flushbuf, fflush и fclose.Упражнение 8.4. Функция стандартной библиотекиint fseek(FILE *fp, long offset, int origin)идентична функции lseek с теми, однако, отличиями, что fp — это файловый указатель, а не дескриптор, ивозвращает она значение int, означающее состояние файла, а не позицию в нем. Напишите свою версиюfseek.
Обеспечьте, чтобы работа вашей fseek по буферизации была согласована с буферизацией,используемой другими функциями библиотеки.8.6. Пример. Печать каталоговПри разного рода взаимодействиях с файловой системой иногда требуется получить только информацию офайле, а не его содержимое. Такая потребность возникает, например, в программе печати каталога файлов,работающей аналогично команде ls системы UNIX. Она печатает имена файлов каталога и по желаниюпользователя другую дополнительную информацию (размеры, права доступа и т.
д.). Аналогичной командойв MS-DOS является dir.Так как в системе UNIX каталог — это тоже файл, функции ls, чтобы добраться до имен файлов, нужно толькоего прочитать. Но чтобы получить другую информацию о файле (например узнать его размер), необходимовыполнить системный вызов.
В других системах (в MS-DOS, например) системным вызовом приходитсяпользоваться даже для получения доступа к именам файлов. Наша цель — обеспечить доступ к информациипо возможности системно-независимым способом несмотря на то, что реализация может быть существенносистемно-зависима.Проиллюстрируем сказанное написанием программы fsize. Функция fsize — частный случай программыls; она печатает размеры всех файлов, перечисленных в командной строке. Если какой-либо из файлов самявляется каталогом, то, чтобы получить информацию о нем, fsize обращается сама к себе.
Если аргументовв командной строке нет, то обрабатывается текущий каталог.Для начала вспомним структуру файловой системы в UNIXe. Каталог — это файл, содержащий список именфайлов и некоторую информацию о том, где они расположены. "Место расположения" — это индекс,обеспечивающий доступ в другую таблицу, называемую "списком узлов inode". Для каждого файла имеетсясвой inode, где собрана вся информация о файле, за исключением его имени. Каждый элемент каталогасостоит из двух частей: из имени файла и номера узла inode.К сожалению, формат и точное содержимое каталога не одинаковы в разных версиях системы. Поэтому,чтобы переносимую компоненту отделить от непереносимой, разобьем нашу задачу на две. Внешнийуровень определяет структуру, названную Dirent, и три подпрограммы opendir, readdir и closedir; врезультате обеспечивается системно-независимый доступ к имени и номеру узла inode каждого элементакаталога.
Мы будем писать программу fsize, рассчитывая на такой интерфейс, а затем покажем, какреализовать указанные функции для систем, использующих ту же структуру каталога, что и Version 7 и SystemV UNIX. Другие варианты оставим для упражнений.Структура Dirent содержит номер узла inode и имя. Максимальная длина имени файла равна NAME_MAX —это значение системно-зависимо. Функция opendir возвращает указатель на структуру, названную DIR (поаналогии с FILE), которая используется функциями readdir и closedir.















