Как упоминалось, число открытых файлов, если оно большое, ограничивается, и вам всегда следует закрывать файлы, когда работа с ними закончена. Если вы этого не сделаете, то в конечном счете выйдете за пределы лимита дескрипторов файлов, создав ситуацию, которая ведет к потере устойчивости части вашей программы.
Система закрывает все открытые файлы, когда процесс завершается, но — за исключением 0, 1 и 2 — плохая манера полагаться на это.
Когда
open
возвращает новый дескриптор файла, она всегда возвращает наименьшее неиспользуемое целое значение. Всегда. Поэтому, если открыты
дескрипторы файлов 0–6 и программа закрывает дескриптор файла 5, следующий вызов
open
вернет 5, а не 7. Это поведение важно; далее в книге мы увидим, как оно используется для аккуратной реализации многих важных особенностей Unix, таких, как перенаправление ввода/вывода и конвейеризация (piping)
4.4.2.1. Отображение переменных
FILE*
на дескрипторы файлов
Стандартные библиотечные функции ввода/вывода и переменные
FILE*
из
<stdio.h>
, такие, как
stdin
,
stdout
и
stderr
, построены поверх основанных на дескрипторах файлов системных вызовах.
Иногда полезно получить непосредственный доступ к дескриптору файла, связанному с указателем файла
<stdio.h>
, если вам нужно сделать что-либо, не определенное стандартом С ISO. Функция
fileno
возвращает лежащий в основе дескриптор файла:
#include <stdio.h> /* POSIX */
int fileno(FILE *stream);
Пример мы увидим позже, в разделе 4.4.4. «Пример: Unix cat».
4.4.2.2. Закрытие всех открытых файлов
Открытые файлы наследуются порожденными процессами от своих родительских процессов. Фактически они являются общими. В частности, общим является положение в файле. Подробности мы оставим для дальнейшего обсуждения в разделе 9.1.1.2 «Разделение дескрипторов файлов».
Поскольку программы могут наследовать другие файлы, иногда вы можете увидеть программы, которые закрывают все свои файлы, чтобы начать с «чистого состояния» В частности, типичен код наподобие этого:
int i;
/* оставить лишь 0, 1, и 2 */
for (i = 3; i < getdtablesize; i++)
(void)close(i);
Предположим, что результат
getdtablesize
равен 1024. Этот код работает, но он делает (1024-3)*2 = 2042 системных вызова.
1020
из них не нужны, поскольку возвращаемое значение
getdtablesize
не изменяется. Вот лучший вариант этого кода:
int i, fds;
for (i = 3, fds = getdtablesize; i < fds; i++)
(void)close(i);
Такая оптимизация не ухудшает читаемость кода, но может быть заметна разница, особенно на медленных системах. В общем, стоит поискать случаи, когда в циклах повторно вычисляется один и тот же результат, чтобы посмотреть, нельзя ли вынести вычисление за пределы цикла. Хотя в таких случаях нужно убедиться, что вы (а) сохраняете правильность кода и (б) сохраняете его читаемость!
Каждая функция сделана как можно проще. Аргументами являются дескриптор открытого файла, указатель на буфер для чтения или записи данных и число читаемых или записываемых байтов.
Возвращаемое значение является числом действительно прочитанных или записанных байтов. (Это число может быть меньше запрошенного: при операции чтения это происходит, когда в файле осталось меньше
count
байтов, а при операции записи это случается, когда диск заполнен или произошла еще какая-нибудь ошибка.) Возвращаемое значение -1 означает возникшую ошибку, в этом случае errno указывает эту ошибку. Когда
read
возвращает 0, это означает, что достигнут конец файла.
Теперь мы можем показать оставшуюся часть кода для
ch04-cat
. Процедура
process
использует 0 для стандартного ввода, если именем файла является «
–
» (строки 50 и 51). В противном случае она открывает данный файл:
36 /*
37 * process --- сделать что-то с файлом, в данном случае,
38 * послать его в stdout (fd 1).
39 * Возвращает 0, если все нормально; в противном случае 1.
40 */
41
42 int
43 process(char *file)
44 {
45 int fd;
46 ssize_t rcount, wcount;
47 char buffer[BUFSIZ];
48 int errors = 0;
49
50 if (strcmp(file, "-") == 0)
51 fd = 0;
52 else if ((fd = open(file, O_RDONLY)) < 0) {
53 fprintf(stderr, "%s: %s: cannot open for reading: %s\n",
54 myname, file, strerror(errno));
55 return 1;
56 }
Буфер
buffer
(строка 47) имеет размер
BUFSIZ
; эта константа определена В
<stdio.h>
как «оптимальный» размер блока для ввода/вывода. Хотя значение
BUFSIZ
различается в разных системах, код, использующий эту константу, чистый и переносимый.
Основой процедуры является следующий цикл, который повторно читает данные до тех пор, пока не будет достигнут конец файла или не возникнет ошибка.