Программирование для Linux. Профессиональный подход - страница 19
>
> 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 показан пример реализации такой функции.
>#include
>#include
>#include
>#include
>#include
>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. Создание и использование библиотек
Практически со всеми программами компонуется одна или несколько библиотек. К любой программе, использующей функции языка С (например, >printf()
или >malloc()
), подключается библиотека времени выполнения. Если у программы есть графический интерфейс, вместе с ней компонуются библиотеки функций работы с окнами. Когда программа обращается к СУБД, она делает это посредством функции библиотеки, предоставленной разработчиком данной СУБД.