RuLibrary.com

ГЛАВНАЯ | ПОИСК | ТОП | КАРТА САЙТА      

 
 


 

Керниган Ричи >> Язык C (страница 24)


Функция OPEN весьма сходна с функцией FOPEN, рассмотрен- ной в главе 7, за исключением того, что вместо возвращения указателя файла она возвращает дескриптор файла, который яв- ляется просто целым типа INT.

INT FD; FD=OPEN(NAME,RWMODE);

Как и в случае FOPEN, аргумент NAME является символьной строкой, соответствующей внешнему имени файла. Однако аргу- мент, определяющий режим доступа, отличен: RWMODE равно: 0 - для чтения, 1 - для записи, 2 - для чтения и записи. Если происходит какая-то ошибка, функция OPEN возвращает "-1"; в противном случае она возвращает действительный дескриптор файла.

Попытка открыть файл, который не существует, является ошибкой. Точка входа CREAT предоставляет возможность созда- ния новых файлов или перезаписи старых. В результате обраще- ния

FD=CREAT(NAME,PMODE);

возвращает дескриптор файла, если оказалось возможным соз- дать файл с именем NAME, и "-1" в противном случае. Если файл с таким именем уже существует, CREAT усечет его до ну- левой длины; создание файла, который уже существует, не яв- ляется ошибкой.

Если файл является совершенно новым, то CREAT создает его с определенным режимом защиты, специфицируемым аргумен- том PMODE. В системе файлов на UNIX с файлом связываются де- вять битов защиты информации, которые управляют разрешением на чтение, запись и выполнение для владельца файла, для группы владельцев и для всех остальных пользователей. Таким образом, трехзначное восьмеричное число наиболее удобно для спецификации разрешений. Например, число 0755 свидетельству- ет о разрешении на чтение, запись и выполнение для владельца и о разрешении на чтение и выполнение для группы и всех ос- тальных.

Для иллюстрации ниже приводится программа копирования одного файла в другой, являющаяся упрощенным вариантом ути- литы CP системы UNIX. (Основное упрощение заключается в том, что наш вариант копирует только один файл и что второй аргу- мент не должен быть справочником).

#DEFINE NULL 0

#DEFINE BUFSIZE 512

#DEFINE PMODE 0644/*RW FOR OWNER,R FOR GROUP,OTHERS*/

MAIN(ARGC,ARGV) /*CP: COPY F1 TO F2*/

INT ARGC;

CHAR *ARGV[];

\(

INT F1, F2, N;

CHAR BUF[BUFSIZE];

IF (ARGC ! = 3)

ERROR("USAGE:CP FROM TO", NULL);

IF ((F1=OPEN(ARGV[1],0))== -1)

ERROR("CP:CAN'T OPEN %S", ARGV[1]);

IF ((F2=CREAT(ARGV[2],PMODE))== -1)

ERROR("CP: CAN'T CREATE %S", ARGV[2]);

WHILE ((N=READ(F1,BUF,BUFSIZE))>0)

IF (WRITE(F2,BUF,N) !=N)

ERROR("CP: WRITE ERROR", NULL);

EXIT(0);

\)

ERROR(S1,S2) /*PRINT ERROR MESSAGE AND DIE*/

CHAR *S1, S2;

\(

PRINTF(S1,S2);

PRINTF("\N");

EXIT(1);

\)

Существует ограничение (обычно 15 - 25) на количество файлов, которые программа может иметь открытыми одновремен- но. В соответствии с этим любая программа, собирающаяся ра- ботать со многими файлами, должна быть подготовлена к пов- торному использованию дескрипторов файлов. Процедура CLOSE прерывает связь между дескриптором файла и открытым файлом и освобождает дескриптор файла для использования с некоторым другим файлом. Завершение выполнения программы через EXIT или в результате возврата из ведущей программы приводит к закрытию всех открытых файлов.

Функция расцепления UNLINK (FILENAME) удаляет из системы файлов файл с именем FILENAME ( из данного справочного фай- ла. Файл может быть сцеплен с другим справочником, возможно, под другим именем - примеч.переводчика).

Упражнение 8-1

Перепишите программу CAT из главы 7, используя функции READ, WRITE, OPEN и CLOSE вместо их эквивалентов из стандар- тной библиотеки. Проведите эксперименты для определения от- носительной скорости работы этих двух вариантов.

8.4. Произвольный доступ - SEEK и LSEEK

Нормально при работе с файлами ввод и вывод осуществля- ется последовательно: при каждом обращении к функциям READ и WRITE чтение или запись начинаются с позиции, непосредствен- но следующей за предыдущей обработанной. Но при необходимос- ти файл может читаться или записываться в любом произвольном порядке. Обращение к системе с помощью функции LSEEK позво- ляет передвигаться по файлу, не производя фактического чте- ния или записи. В результате обращения

LSEEK(FD,OFFSET,ORIGIN);

текущая позиция в файле с дескриптором FD передвигается на позицию OFFSET (смещение), которая отсчитывается от места, указываемого аргументом ORIGIN (начало отсчета). Последующее чтение или запись будут теперь начинаться с этой позиции. Аргумент OFFSET имеет тип LONG; FD и ORIGIN имеют тип INT. Аргумент ORIGIN может принимать значения 0,1 или 2, указывая на то, что величина OFFSET должна отсчитываться соответст- венно от начала файла, от текущей позиции или от конца фай- ла. Например, чтобы дополнить файл, следует перед записью найти его конец:

LSEEK(FD,0L,2);

чтобы вернуться к началу ("перемотать обратно"), можно напи- сать:

LSEEK(FD,0L,0);

обратите внимание на аргумент 0L; его можно было бы записать и в виде (LONG) 0.

Функция LSEEK позволяет обращаться с файлами примерно так же, как с большими массивами, правда ценой более медлен- ного доступа. следующая простая функция, например, считывает любое количество байтов, начиная с произвольного места в файле.

GET(FD,POS,BUF,N) /*READ N BYTES FROM POSITION POS*/

INT FD, N;

LONG POS;

CHAR *BUF;

\(

LSEEK(FD,POS,0); /*GET TO POS*/

RETURN(READ(FD,BUF,N));

\)

В более ранних редакциях, чем редакция 7 системы UNIX, основная точка входа в систему ввода-вывода называется SEEK. Функция SEEK идентична функции LSEEK, за исключением того, что аргумент OFFSET имеет тип INT, а не LONG. в соответствии с этим, поскольку на PDP-11 целые имеют только 16 битов, ар- гумент OFFSET, указываемый функции SEEK, ограничен величиной 65535; по этой причине аргумент ORIGIN может иметь значения 3, 4, 5, которые заставляют функцию SEEK умножить заданное значение OFFSET на 512 (количество байтов в одном физическом блоке) и затем интерпретировать ORIGIN, как если это 0, 1 или 2 соответственно. Следовательно, чтобы достичь произ- вольного места в большом файле, нужно два обращения к SEEK: сначала одно, которое выделяет нужный блок, а затем второе, где ORIGIN имеет значение 1 и которое осуществляет передви- жение на желаемый байт внутри блока.

Упражнение 8-2

Очевидно, что SEEK может быть написана в терминалах LSEEK и наоборот. напишите каждую функцию через другую.

8.5. Пример - реализация функций 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*/ );

Из инициализации части _FLAG этого массива структур видно, что файл STDIN предназначен для чтения, файл STDOUT - для записи и файл STDERR - для записи без использования буфера.

Упражнение 8-3

Перепишите функции FOPEN и _FILEBUF, используя поля вместо явных побитовых операций.

Упражнение 8-4

Разработайте и напишите функции _FLUSHBUF и FCLOSE.

Упражнение 8-5

Стандартная библиотека содержит функцию

FSEEK(FP, OFFSET, ORIGIN)

которая идентична функции LSEEK, исключая то, что FP являет- ся указателем файла, а не дескриптором файла. Напишите FSEEK. Убедитесь, что ваша FSEEK правильно согласуется с бу- феризацией, сделанной для других функций библиотеки.

8.6. Пример - распечатка справочников

Название книги: Язык C
Автор: Керниган Ричи
Просмотрено 45105 раз

123456789101112131415161718192021222324252627282930313233


 
Page generation 0.005 seconds