Часто возникает ситуация, когда во время работы функции возникает какая-то ошибка. При этом надо, помимо возврата кода ошибки, освободить выделенные до этого ресурсы.
Итак, несколько способов это сделать:
Первый. Интуитивный.
int someFuntion( ... )
{
int *someResource1 = NULL;
char *someResource2 = NULL;
float *someResource3 = NULL;
/* Initialization block */
if ((someResource1 = malloc(sizeof(int) * 5)) == NULL)
return ERROR1;
if ((someResource2 = malloc(sizeof(char) * 7)) == NULL)
{
free(someResource1);
return ERROR2;
}
if ((someResource3 = malloc(sizeof(float) * 9)) == NULL)
{
free(someResource1);
free(someResource2);
return ERROR3;
}
/* Some code here */
...
return SUCCESS;
}
Из преимуществ разве что интуитивная понятность.
Из минусов:
- Дублирование кода
- Необходимость каждый раз вспоминать, что выделялось сверху.
- Плохая модифицируемость
- Нечитаемость
Второй. Абстрактный.
int someFuntion( ... )
{
int *someResource1 = NULL;
char *someResource2 = NULL;
float *someResource3 = NULL;
int errorCode = 0;
/* Initialization block */
errorCode = allocateResources(someResource1, someResource2, someResource3);
if (!errorCode)
{
/* Some code here */
}
else
handleError(errorCode);
releaseResources(someResource1, someResource2, someResouce3);
return errorCode;
}
Из преимуществ:
- Компактность
- Все действия находятся на одном уровне абстракции
- Хорошая модифицируемость
- Одна точка выхода из функции
Из минусов:
- Для каждой функции надо писать свои функции выделения и освобождения памяти
- Все "ломается" когда ошибки могут возникать не только на этапе инициализации (вложенные циклы, например)
Третий. Нелокальные переходы.
#include <setjmp.h>
jmp_buf g_jumpBuffer;
int someFunction1( void )
{
int errorCode = 0;
int *someResource = NULL;
/* При первом проходе вернет 0, но при вызове longjmp прыгнет на setjmp и вернет код, переданный в longjmp */
if (errorCode = setjmp(g_jumpBuffer))
{
/* Release resources */
if (someResource)
free(someResource);
return errorCode;
}
if ((someResource = malloc(sizeof(int) * 10)) == NULL)
longjmp(g_jumpBuffer, ERROR1);
/* Some code here */
while (someIsValid())
{
/* В случае ошибки */
longjmp(g_jumpBuffer, ERROR2);
}
longjmp(g_jumpBuffer, SUCCESS);
}
Для начала
статья про нелокальные переходы.
Из преимуществ:
- Компактность. Обработка ошибок находится в одном месте.
- Можно "прыгнуть" из любого места функции. (При желании даже из другой функции этого модуля).
- Хорошая модифицируемость
- Одна точка выхода из функции
Из минусов:
- Можно запутаться, если прыгать из фунции в функцию
Четвертый. goto.
#define CATCH_ERROR(errorcode, errorlabel) \
errorCode = errorcode; \
goto errorlabel
int someFunction( void )
{
int errorCode = 0;
int *someResource = NULL;
if ((someResource = malloc(sizeof(int) * 10)) == NULL)
CATCH_ERROR(ERROR1, errorLabel);
/* Some code here */
while (someIsValid())
{
/* В случае ошибки */
CATCH_ERROR(ERROR2, errorLabel);
}
errorLabel:
/* Release resource */
if (someResource)
free(someResource);
return errorCode;
}
Из преимуществ:
- Компактность. Обработка ошибок находится в одном месте.
- Можно "прыгнуть" из любого места функции.
- Хорошая модифицируемость.
- Одна точка выхода из функции.
- Хороший метод, если ты поклонник
goto
Из минусов:
- Неявная инициализация кода ошибки. В функции обязана быть переменная
errorCode
. (см. define
)
- Ужастный метод, если ты ненавидишь
goto
. И вообще, все, кто так делает будут гореть в аду.