0. Освобождение ресурсов
В этом примере рассматривается код, в котором много условий завершения работы программы (не выделилась память, не открылся файл и пр.) и нужно освобождать выделенные ресурсы.
int main(void)
{
FILE *inputfile = fopen(...);
int *a = malloc(5 * 4);
if (!a)
{
fclose(inputfile);
}
if (fscanf(inputfile, ...) != 1)
{
fclose(inputfile);
free(a);
}
// do smth
}
Вариант 1 реорганизации и исправления кода через метки и goto
В этом примере показано, что можно завести метку на то место (близкое к концу программы), где освобождаются ресурсы. Важно помнить, что перед освобождением ресурса следует проверить, а выделялся ли он вообще, и если да, то освобождать.
Таким образом, код освобождения ресурсов прописан один раз. Также бонусов является тот момент, что сообщение об ошибке пишется в if
, из которого "логически" начинается завершение программы, и в этом месте пользователю можно выдать детальное сообщение об ошибке (указать в fprintf
параметры после строки формата).
int main(void)
{
int result = SUCCESS;
FILE *inputfile = NULL;
int *a = NULL;
inputfile = fopen(name, "r");
if (!inputfile)
{
fprintf(stderr, "can't open file: %s\n", name);
result = ERROR_CANNOT_OPEN_FILE;
goto lCleanup;
}
if (fscanf(inputfile, ...) != 1)
{
fprintf(stderr, "index error\n");
result = ERROR_DATA_INVALID;
goto lCleanup;
}
a = malloc(5 * sizeof(int));
if (!a)
{
fprintf(stderr, "can't allocate memory\n");
result = ERROR_OUT_OF_MEMORY;
goto lCleanup;
}
// do smth
lCleanup:
free(a);
if (inputfile)
fclose(inputfile);
return result;
}
Примечание про выделение памяти и VLA-указатели (не массивы!)
Если массив хранится в виде `double(*A)[N] = malloc(sizeof(double[N]) * N);`, то при попытке компиляции можно словить ошибку вида:
main.c:*:*: error: jump into scope of identifier with variably modified type
Решить эту проблему можно следующим образом - вся работа с массивом спрятана в блок и очищение ресурсов разбито на 2 части:
<код до выделение памяти>
// все переходы идут на метку cleanup1
// выделение памяти и работу почти всего основного загоняем в отдельный блок
{
выделение памяти для A
// все переходы идут на метку cleanup2
cleanup2: ...
}
cleanup1: ...
Вариант 2 реорганизации и исправления кода с использованием структуры
Здесь показан способ задания структуры Context
, содержащей указатели на выделяемые ресурсы. При необходимости также можно добавлять и какие-то переменные. Для освобождения ресурсов прописывается функция release
, которая занимается освобождением ресурсов по аналогии с первым примером.
Как и в первом варианте код освобождения ресурсов прописывается один раз, однако теряется детализация сообщения об ошибке - здесь сообщение "хардкодится" в таблице ошибок error_message_table
.
Context context[1] = {NULL};
- в этом месте объявляется массив на один элемент для однообразия обращения к полям структуры во всём коде - через ->
.
#include <stdlib.h>
#include <stdio.h>
#define SUCCESS 0
#define ERROR_CANNOT_OPEN_FILE 1
#define ERROR_DATA_INVALID 2
#define ERROR_OUT_OF_MEMORY 3
typedef struct
{
FILE *inputfile;
int *a;
} Context;
// error_message_table - таблица, в которой индексы означают значения кодов возврата (0, 1, 2 и т.д.), а содержимое каждой ячейки - унифицированное сообщение об ошибке
char *error_message_table[] =
{
"", // SUCCESS
"cannot open file", // ERROR_CANNOT_OPEN_FILE
"invalid data", //ERROR_DATA_INVALID
"out of memory" // ERROR_OUT_OF_MEMORY
};
int release_context(Context *context, int error_code)
{
free(context->a);
if (context->inputfile)
fclose(context->inputfile);
if (error_code)
fprintf(stderr, "%s\n", error_message_table[error_code]);
return error_code;
}
int main(void)
{
Context context[1] = {NULL};
context->inputfile = fopen("a", "r");
if (!context->inputfile)
return release_context(context, ERROR_CANNOT_OPEN_FILE);
int n;
if (fscanf(context->inputfile, "%i", &n) != 1)
return release_context(context, ERROR_DATA_INVALID);
context->a = malloc(n * sizeof(*context->a));
if (!context->a)
return release_context(context, ERROR_OUT_OF_MEMORY);
return release_context(context, SUCCESS);
}
Уточнение про error_message_table
Если вам никто не гарантирует, что коды всегда начинаются с 0 и идут последовательно друг за другом и в дальнейшем коды меняться не будут, то можно завести простой массив вида
char *error_message_table[] = {
"", // SUCCESS
"Cannot open file", // ERROR_CANNOT_OPEN_FILE
...
}
char *error_message_table[] = {
[SUCCESS]="",
[ERROR_CANNOT_OPEN_FILE]="Cannot open file",
...
}
Last updated