Выбрать главу

#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);

Такая оптимизация не ухудшает читаемость кода, но может быть заметна разница, особенно на медленных системах. В общем, стоит поискать случаи, когда в циклах повторно вычисляется один и тот же результат, чтобы посмотреть, нельзя ли вынести вычисление за пределы цикла. Хотя в таких случаях нужно убедиться, что вы (а) сохраняете правильность кода и (б) сохраняете его читаемость!

4.4.3. Чтение и запись

Ввод/вывод осуществляется системными вызовами read() и write() соответственно:

#include <sys/types.h> /* POSIX */

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

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

Возвращаемое значение является числом действительно прочитанных или записанных байтов. (Это число может быть меньше запрошенного: при операции чтения это происходит, когда в файле осталось меньше 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 различается в разных системах, код, использующий эту константу, чистый и переносимый.

Основой процедуры является следующий цикл, который повторно читает данные до тех пор, пока не будет достигнут конец файла или не возникнет ошибка.

58 while ((rcount = read(fd, buffer, sizeof buffer)) > 0) {

59  wcount = write(1, buffer, rcount);

60  if (wcount != rcount) {

61   fprintf(stderr, "%s: %s: write error: %s\n",

62    myname, file, strerror(errno));

63   errors++;