Обработка ошибок в си
Навеяно вознёй со своим проектом, и вот этим срачем "как правильно закрывать файл" на 18 страниц. На полноту охвата вопроса не претендую, только то что видел и использовал сам.
Будем рассматривать на примере выделения памяти для сложного ресурса, сферичной структуры в вакууме с несколькими членами, также выделяемыми через *alloc(). Вот такой:
struct foo {
int id;
char *data1;
char *data2;
};
Сразу оговорюсь, что я считаю использование функций-аллокаторов "всего и сразу" - плохой практикой.
Лучшим способом мне кажется подход, когда сначала выделяется сама структура, а в функции, скажем, bool foo_init(struct *foo) - выделяются все её компоненты (и там же освобождаются при неудаче). Т.е. ресурсы не должны "всплывать" вверх по стеку через return.
Это позволяет писать код однообразно с тем, когда такая структура выделяется просто на стеке.
Лестница if'ов
Он же "дийкстра-стиль".
struct *foo
new_foo() {
struct *t = NULL;
t = calloc(1, sizeof(foo));
if (t) {
t->data1 = calloc(48, sizeof(char));
if (t->data1) {
t->data2 = calloc(64, sizeof(char));
if (t->data2) {
return t;
}
free(t->data1);
}
free(t);
}
return NULL;
}
Недостатки: выглядит страшно, при большом отступе неудобно читать.
Преимущества: даже с кучерявыми руками можно добиться корректного освобождения ресурсов, при соблюдении правила "один вход/один выход".
Магический switch
Вариация предыдущего пункта.
struct *foo
new_foo() {
struct *t = NULL;
int stage = 0;
t = calloc(1, sizeof(foo));
if (t) {
stage++;
t->data1 = calloc(48, sizeof(char));
if (t->data1) {
stage++
t->data2 = calloc(64, sizeof(char));
if (t->data2) {
return t;
}
}
}
switch (stage) {
case 2 : free(t->data1);
case 1 : free(t);
default :
break;
}
return NULL;
}
Эта техника называется "на что только не идут люди, чтобы goto не использовать". Используется тот факт, что в case без break выполнение продолжается на следующее условие.
Можно не int, а enum, тогда stage станет читаемым, и можно обойтись без default, компилятор ругнётся, если мы перечислили не все означенные case.
goto cleanup
Великий и ужасный goto. Широко распространённая практика, используется, например, в том же openssl1 или ядре linux.
struct *foo
new_foo() {
struct *t = NULL;
t = calloc(1, sizeof(foo));
if (!t)
goto cleanup;
t->data1 = calloc(48, sizeof(char));
t->data2 = calloc(64, sizeof(char));
if (!t->data1 || !t->data2))
goto cleanup;
return t;
/* но кажется что-то пошло не так... */
cleanup:
if (t) {
free(t->data1);
free(t->data2);
}
free(t);
return NULL;
Тут есть одна хитрость: все alloc() рекомендуется собрать в кучу вынести вниз, после всех проверок насколько возможно. Тогда в начале функции до первого alloc() можно использовать return, а не goto.
do-false
Вариант предыдущего пункта. Уходим от использования goto через более ограниченный break.
struct *foo
new_foo() {
struct *t = NULL;
do {
t = calloc(1, sizeof(foo));
if (!t)
break;
t->data1 = calloc(48, sizeof(char));
t->data2 = calloc(64, sizeof(char));
if (!t->data1 || !t->data2))
break;
return t;
} while (0);
if (t) {
free(t->data1);
free(t->data2);
}
free(t);
return NULL;
}
Более прямолинеен чем предыдущий вариант, легче читать.
use the stack, Luke!
В случае, если с такой структурой надо работать в той же функции - можно сэкономить на вызовах free(), использовав alloca(). Пишу на правах КО.
Magick-way
Ещё одна интересная техника, подсмотренная в ImageMagick. Используется тот факт, что большинство функций там имеют унифицированные коды возврата. То же самое видел и в сишном api sqlite.
Впрочем подойдут любые несколько функций, возвращающие то же самое значение при успехе.
if (status == MagickPass)
status = MagickNormalizeImage(wand);
if (status == MagickPass)
status = MagickThresholdImage(wand, 50.0 * (MaxRGB / 100));
if (status == MagickPass)
status = MagickSetImageType(wand, BilevelType);
if (status == MagickPass)
status = MagickSetImageFormat(wand, "MONO");
/* длинная простыня подобного */
if (status != MagickPass) {
/* где-то выше сфейлилось, врубаем panic-mode и жарим карри */
description = MagickGetException(wand, &severity);
fprintf(stderr, "%03d %.1024s\n", severity, description);
DestroyMagickWand(wand);
return NULL;
}
хотя его нельзя назвать образцом для подражания. ↩