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

  /* Что-то неправильно с файлом, выводим сообщение

     об ошибке. */

  fprintf(stderr, "error changing ownership of %s: %s\n",

   path, strerror(error_code));

  /* He завершаем программу; можно предоставить пользователю

     шанс открыть другой файл. */

  break;

 case ЕFAULT:

  /* Переменная PATH содержит неправильный адрес. Это, скорее

     всего, ошибка программы. */

  abort();

 case ENOMEM:

  /* Ядро столкнулось с нехваткой памяти. */

  fprintf(stderr, "%s\n", strerror(error_code));

  exit(1);

 default:

  /* Произошла какая-то другая, непредвиденная ошибка. Мы

     пытались обработать все возможные коды ошибок. Если

     что-то пропущено, то это ошибка программы! */

  abort();

 };

}

В самом начале программного фрагмента можно было поставить следующий код:

rval = chown(path, user_id, -1);

assert(rval == 0);

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

2.2.4. Ошибки выделения ресурсов

Обычно при неудачном выполнении системного вызова наиболее приемлемое решение — отменить текущую операцию, но не завершить программу, так как можно восстановить ее нормальную работу. Один из способов сделать это — выйти из текущей функции, передав через оператор return код ошибки вызывающему модулю.

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

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

1. выделяет буфер;

2. открывает файл;

3. читает содержимое файла и записывает его в буфер;

4. закрывает файл;

5. возвращает буфер вызывающему модулю.

Если файл не существует, этап 2 закончится неудачей. Подходящая реакция в этом случае — вернуть из функции значение NULL. Но если буфер уже был выделен на этапе 1, существует опасность потери этого ресурса. Нужно не забыть освободить буфер где-то в программе. Если же неудачей завершится этап 3, требуется не только освободить буфер перед выходом из функции, но и закрыть файл.

В листинге 2.6 показан пример реализации такой функции.

Листинг 2.6. (readfile.c) Освобождение ресурсов при возникновении аварийных ситуаций

#include <fcntl.h>

#include <stdlib.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <unistd.h>

char* read_from_file(const char* filename, size_t length) {

 char* buffer;

 int fd;

 ssize_t bytes_read;

 /* Выделяем буфер. */

 buffer = (char*)malloc(length);

 if (buffer == NULL)

  return NULL;

 /* Открываем файл. */

 fd = open(filename, O_RDONLY);

 if (fd == 1) {

  /* Открыть файл не удалось. Освобождаем буфер

     перед выходом. */

  free(buffer);

  return NULL;

 }

 /* Чтение данных. */

 bytes_read = read(fd, buffer, length);

 if (bytes_read != length) {

  /* Чтение не удалось. Освобождаем буфер и закрываем файл

     перед выходом. */

  free(buffer);

  close(fd);

  return NULL;

 }

 /* Все прошло успешно. Закрываем файл и возвращаем буфер

    в программу. */

 close(fd);

 return buffer;

}

При завершении программы операционная система Linux освобождает выделенную память, ссылки на открытые файлы и большинство других ресурсов, поэтому перед вызовом функции exit() нет необходимости удалять буферы и закрывать файлы. Но некоторые другие совместно используемые ресурсы приходится все же освобождать вручную. В частности, это относится к временным файлам и совместным буферам памяти: они способны "пережить" программу.

2.3. Создание и использование библиотек