Вскоре мы увидим пример кода. Дополнительные значения
flags
описаны в разделе 4.6 «Создание файлов». Большой объем ранее написанного кода Unix не использовал эти символические значения. Вместо этого использовались числовые значения. Сегодня это рассматривается как плохая практика, но мы представляем эти значения, чтобы вы их распознали, если встретитесь с ними
Системный вызов
close
закрывает файл: его элемент в системной таблице дескрипторов файлов помечается как неиспользуемый, и с этим дескриптором нельзя производить никаких дальнейших действий. Объявление следующее:
#include <unistd.h> /* POSIX */
int close(int fd);
В
случае успеха возвращается 0, при ошибке (-1). При возникновении ошибки нельзя ничего сделать, кроме сообщения о ней. Ошибки при закрытии файлов являются необычными, но не невозможными, особенно для файлов, доступ к которым осуществляется через сеть. Поэтому хорошей практикой является проверка возвращаемого значения, особенно для файлов, открытых для записи.
Если вы будете игнорировать возвращаемое значение, специально приведите его к типу
void
, чтобы указать, что вам не нужен результат:
(void)close(fd); /* отказ от возвращаемого значения */
Легкомысленность этого совета в том, что слишком большое количество приведений к
void
имеют тенденцию загромождать код. Например, несмотря на принцип «всегда проверять возвращаемое значение», чрезвычайно редко можно увидеть код, проверяющий возвращаемое значение
printf
или приводящий его к
void
. Как и со многими аспектами программирования на С, здесь также требуются опыт и рассудительность.
Как упоминалось, число открытых файлов, если оно большое, ограничивается, и вам всегда следует закрывать файлы, когда работа с ними закончена. Если вы этого не сделаете, то в конечном счете выйдете за пределы лимита дескрипторов файлов, создав ситуацию, которая ведет к потере устойчивости части вашей программы.
Система закрывает все открытые файлы, когда процесс завершается, но — за исключением 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, это означает, что достигнут конец файла.