Язык программирования Си. Издание 3-е, исправленное - Брайан Керниган
Шрифт:
Интервал:
Закладка:
error("cp: не могу открыть файл %s", argv[1]);
if ((f2 = creat(argv[2], PERMS)) == -1)
error("cp: не могу создать файл %s, режим %03o", argv[2], PERMS);
while ((n = read(f1, buf, BUFSIZ)) › 0)
if (write(f2, buf, n) != n)
error ("cp: ошибка при записи в файл %s", argv[2]);
return 0;
}
Данная программа создаст файл вывода с фиксированными правами доступа, определяемыми кодом 0666. С помощью системного вызова stat, который будет описан в параграфе 8.6, мы можем определить режим использования существующего файла и задать тот же режим для копии.
Заметим, что функция error, вызываемая с различным числом аргументов, во многом похожа на printf. Реализация error иллюстрирует, как пользоваться другими программами семейства printf. Библиотечная функция vprintf аналогична printf, с той лишь оговоркой, что переменная часть списка аргументов заменена в ней одним аргументом, который инициализируется макросом va_start. Подобным же образом соотносятся функции vfprinf с fprintf и vsprintf с sprintf.
#include ‹stdio.h›
#include ‹stdarg.h›
/* error: печатает сообщение об ошибке и умирает */
void error(char *fmt, …) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "ошибка: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "n");
va_end(args);
exit(1);
}
На количество одновременно открытых в программе файлов имеется ограничение (обычно их число колеблется около 20). Поэтому любая программа, которая намеревается работать с большим количеством файлов, должна быть готова повторно использовать их дескрипторы. Функция close(int fd) разрывает связь между файловым дескриптором и открытым файлом и освобождает дескриптор для его применения с другим файлом. Она аналогична библиотечной функции fclose с тем лишь различием, что никакой очистки буфера не делает. Завершение программы с помощью exit или return в главной программе закрывает все открытые файлы.
Функция unlink(char *name) удаляет имя файла из файловой системы. Она соответствует функции remove стандартной библиотеки.
Упражнение 8.1. Перепишите программу cat из главы 7, используя функции read, write, open и close. Замените ими соответствующие функции стандартной библиотеки. Поэкспериментируйте, чтобы сравнить быстродействие двух версий.
8.4 Произвольный доступ (lseek)
Ввод-вывод обычно бывает последовательным, т. е. каждая новая операция чтения-записи имеет дело с позицией файла, следующей за той, что была в предыдущей операции (чтения-записи). При желании, однако, файл можно читать или производить запись в него в произвольном порядке. Системный вызов lseek предоставляет способ передвигаться по файлу, не читая и не записывая данные. Так, функция
long lseek(int fd, long offset, int origin);
в файле с дескриптором fd устанавливает текущую позицию, смещая ее на величину offset относительно места, задаваемого значением origin. Значения параметра origin 0, 1 или 2 означают, что на величину offset отступают соответственно от начала, от текущей позиции или от конца файла. Например, если требуется добавить что-либо в файл (когда в командном интерпретаторе shell системы UNIX ввод перенаправлен оператором ›› в файл или когда в fopen задан аргумент "a"), то прежде чем что-либо записывать, необходимо найти конец файла с помощью вызова функции
lseek(fd, 0L, 2);
Чтобы вернуться назад, в начало файла, надо выполнить
lseek(fd, 0L, 0);
Следует обратить внимание на аргумент 0L: вместо 0L можно было бы написать (long)0 или, если функция lseek должным образом объявлена, просто 0. Благодаря lseek с файлами можно работать так, как будто это большие массивы, правда, с замедленным доступом. Например, следующая функция читает любое число байтов из любого места файла. Она возвращает число прочитанных байтов или -1 в случае ошибки.
#include "syscalls.h"
/* get: читает n байт из позиции pos */
int get(int fd, long pos, char *buf, int n)
{
if (lseek(fd, pos, 0) ›= 0) /* установка позиции */
return read(fd, buf, n);
else
return -1;
}
Возвращаемое функцией lseek значение имеет тип long и является новой позицией в файле или, в случае ошибки, равно -1. Функция fseek из стандартной библиотеки аналогична lseek: от последней она отличается тем, что в случае ошибки возвращает некоторое ненулевое значение, а ее первый аргумент имеет тип FILE*.
8.5 Пример. Реализация функций fopen и getc
Теперь на примере функций fopen и getc из стандартной библиотеки покажем, как описанные выше части согласуются друг с другом.
Напомним, что файлы в стандартной библиотеке описываются файловыми указателями, а не дескрипторами. Указатель файла - это указатель на структуру, содержащую информацию о файле: указатель на буфер, позволяющий читать файл большими кусками: число незанятых байтов буфера; указатель на следующую позицию в буфере; дескриптор файла; флажки, описывающие режим (чтение/запись), ошибочные состояния и т. д.
Структура данных, описывающая файл, содержится в ‹stdio.h›, который необходимо включать (с помощью #include) в любой исходный файл, если в том осуществляется стандартный ввод-вывод. Этот же заголовочный файл включен и в исходные тексты библиотеки ввода-вывода.
В следующем фрагменте, типичном для файла ‹stdio.h›, имена, используемые только в библиотечных функциях, начинаются с подчеркивания. Это сделано для того, чтобы они случайно не совпали с именами, фигурирующими в программе пользователя. Такое соглашение соблюдается во всех программах стандартной библиотеки.
#define NULL 0
#define EOF (-1)
#define BUFSIZ 1024
#define OPEN_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: открывает файл, возвращает файловый указатель */