LINUX.ORG.RU

С: Освобождение памяти и закрытие файлов


0

0

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

Как Вы считаете, как в данном случае корректнее всего освобождать пямять и закрывать файлы? Проверять все инициализованные переменные типа type* и ранее открытые файлы в каждом случае проверки ошибок? Полагаться на саму программу, что она корректно освободит пямять при завершении? Использовать глобальные переменные и в слечае ошибки вызывать некую функцию finalize(), которая закрывает все файлы, освобождает пяиять и завершает работу с exit(1)? Или даже использовать goto?

Заранее спасибо.

★★

Мне кажется полагаться на то, что ОС закроет файл/освободит память -
это некрасиво. Например, если ты захочешь потом использовать этот код
как часть некоторой программы (и освобождение памяти при завершении программы будет недопустимым решением). Могу предложить три механизма обработки ошибок и
освобождения ресурсов:

1. Можно эмулировать исключения C++. Что-то вроде:

jmpstack stack; // Глобальный стек jmp_buf элементов

Обработка ошибок:

[...]
jmp_buf* buf = stack_push_new(stack); // Добавляет в стек новый элемент типа jmp_buf

FILE* somefile = NULL;
int result = setjmp(*buf); // Аналог try
if(result == 0)
{
    // работаем...

    stack_pop(stack); // убираем обработчик
}
else
{
    // обработка ошибок (аналог catch(...))
    if(somefile != NULL)
        fclose(somefile);

    if(result != SOME_EXCEPTION_TYPE) // Аналог catch(SomeException) - все что кроме пропускаем дальше
        longjmp(stack_pop(stack), result);
}

Инициация ошибок:
[...]
if(someerror)
{
    [...] // Освободить все локальные ресурсы
    longjmp(stack_pop_last(stack), SOME_EXCEPTION_TYPE); // Аналог throw
}
[...]

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


2. Далее можно эмулировать деструкторы глобальных объектов
(собственно, они аналогичным образом и организуются):

stack res_stack;

[...]
res = get_resource();
stack_push(res_stack, res);
atexit(&freeresource); // Тоже LIFO, поэтому будет соответствие между
                       // элементами стека atexit и стека res_stack
[...]

void freeresource()
{
    res = stack_pop(res_stack);
    free_resource(res);
}

3. Ну и локальная обработка ошибок:

if(get_resource1(&res1) == ERROR)
    goto handle_error;

if(get_resource2(&res2) == ERROR)
    goto handle_error;

[...]

return OK;

handle_error:
if(res1 != NULL)
    free_resource1(res1);
if(res2 != NULL)
    free_resource2(res2);

return ERROR;

Вместо goto можно использовать и вложенные if-ы, и.т.д. и.т.п.

Собственно, вторая и третья техники вполне заменяют первую. Если файлы 
открываются в процессе и закрываются только при завершении программы -
то третья техника хорошо подходит для освобождения ресурсов.

Такие вот соображения...

WFrag ★★★★
()
Ответ на: комментарий от WFrag

Спасибо! А как вообще стоит относиться к goto? Много читал флейма по этому поводу... Как "если решение с ним красиво и не нарушает общей логики программы, то нужно использовать?"

octy ★★
() автор топика
Ответ на: комментарий от octy

>Спасибо! А как вообще стоит относиться к goto? Много читал флейма по этому поводу... Как "если решение с ним красиво и не нарушает общей логики программы, то нужно использовать?"

Если знаешь что делаешь и можешь дать обоснованный отпор - используй не стесняясь. :) Например, в вышеприведенной обработке ошибок goto смотрится вполне пристойной и хорошей полноценной замены все равно нет, если не рассматривать кучу вложенных if-ов или вот такие конструкции: if(func1() || func2() || func3() || ... funcN()) { // Ошибка! }

Хотя это скорее дело вкуса.

WFrag ★★★★
()

> Использовать глобальные переменные и в слечае ошибки вызывать некую
> функцию finalize(), которая закрывает все файлы, освобождает пяиять
> и завершает работу с exit(1)

Я считаю это корректным и использую для этих целей atexit(3).

signal11
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.