metaclass: (Default)
metaclass ([personal profile] metaclass) wrote2012-04-21 09:12 pm

Чистый C, обработка ошибок

Это, а как в C принято обрабатывать ошибки?
Т.е. обычная программа: я открываю всякие ком-порты, сокеты и файлы, что-то с ними пытаюсь делать, затем закрываю. В дельфи/java/C#/Clojure это всегда делается через обработку исключений в виде try-finally/using или чего-то аналогичного, в C++ - RAII, а вот что делать в С? Аналогично, с выводом сообщений об ошибке - try{..}catch(Exception e) {logger.Fatal(e};raise}

Я каждую вызываемую функцию проверяю на адекватность возвращаемого результата и при ошибке вывожу в stderr сообщение и strerror(errno), причем выглядит это достаточно единообразно, чтобы хотелось автоматизировать, но принято ли делать хитрожопые макросы типа CHECKERROR(some_call(),"some_call failed") и из них вываливаться из программы при ошибках?

А, и это - принято ли в C заниматься конкатенацией строк по поводу и без повода? А то, скажем, я привык в простых программах особо не мудрить и при необходимости складывать строки, если нет явных требований к производительности. Например, какая-нибудь дурь типа генерации строковых команд из шаблонов и параметров - тупо поскладывал строки и вернул результат. А в С придется strcat использовать, буфера какие-то объявлять, память выделять, трястись за ее удаление или же писать результат прямо в выходной файл, что вообще tight coupling.

[identity profile] denisioru.livejournal.com 2012-04-21 06:40 pm (UTC)(link)
Ну сделай макрос CHECKERROR(some_call(),"some_call failed",finalizer)

где finalizer - функция, подчищающая говно за some_call

[identity profile] metaclass.livejournal.com 2012-04-21 06:45 pm (UTC)(link)
финализеру нужно подчищать все, что успешно отработало ДО него.

[identity profile] nicka-startcev.livejournal.com 2012-04-21 07:03 pm (UTC)(link)
эээ.. man atexit ?

[identity profile] w00dy.livejournal.com 2012-04-21 07:07 pm (UTC)(link)
Тю, делай код в стиле:
CHECK (AllocateResource1, Fin0) ;
CHECK (AllocateResource2, Fin1) ;
CHECK (AllocateResource3, Fin2) ;

use resources

FreeResource3 ;
Fin2:
FreeResource2 ;
Fin1:
FreeResource1 ;
Fin0:


и макрос
CHECK (func, label) \
if (!func ())
goto label ;
Edited 2012-04-21 19:09 (UTC)

[identity profile] mehanizator.livejournal.com 2012-04-21 07:11 pm (UTC)(link)
sprintf не решит проблему конкатенации?

[identity profile] vp.livejournal.com 2012-04-21 07:12 pm (UTC)(link)
Имеется ввиду финализироваться по возврату из проблемного кода.

[identity profile] metaclass.livejournal.com 2012-04-21 07:28 pm (UTC)(link)
snprintf наверно да. Если знать, что размеры результатирующей строки не вылезут за пределы буфера.

[identity profile] sbj-ss.livejournal.com 2012-04-21 07:38 pm (UTC)(link)
Не так всё просто, под строку же выделена память финитного размера - неважно, в стеке или в куче.
snprintf и микрософтовкий sprintf_s проблему не убирают - строка рискует просто быть обрезанной.
Так что в простом варианте realloc и strcat, в параметризованном - scprintf, malloc, sprintf, free старой строки.
Я в своё время, крепко намаявшись, написал относительно универсальное решение.

[identity profile] familom.livejournal.com 2012-04-21 07:39 pm (UTC)(link)
Возврат ресурсов при обработке — через метки и goto. Например, goto failed_config_opening, goto success и т.п.

[identity profile] sbj-ss.livejournal.com 2012-04-21 07:43 pm (UTC)(link)
Да там народ издевается, как может.
Видел и такое:
int stage = 0;
if (func1() < 0)
  goto done;
stage = 1;
if (func2() < 0)
  goto done;
stage = 2;
if (func3() < 0)
  goto done;
done:
switch(stage)
{
  case 2:
    free_res2();
  case 1:
    free_res1();
  default:
    break;
}

Кстати, что забавно - встречено в MSDN :)

[identity profile] vp.livejournal.com 2012-04-21 07:46 pm (UTC)(link)
Какой феерический трындец.

[identity profile] sbj-ss.livejournal.com 2012-04-21 07:55 pm (UTC)(link)
Кстати, незаслуженно забыты setjmp/longjmp.
В частности, разработчики libjpeg укурились настолько, что у них не предусмотрен возврат в программу после возникновения ошибки открытия файла (ну а что, не тот формат и бабац). GIF'ов с расширением .jpg на просторах инета хватает.
Пришлось откровенно издеваться.

[identity profile] justy-tylor.livejournal.com 2012-04-21 07:59 pm (UTC)(link)
Да, хитрожопые макросы с множеством goto.

Особенно забавно это делать на телефонах, когда в любой момент может кончиться память. И тогда надо последовательно освобождать все ресурсы графической подсистемы на данный кадр, а затем кидать в JVM эксепшн "сотонинская жаба, убери мусор".

[identity profile] sbj-ss.livejournal.com 2012-04-21 08:05 pm (UTC)(link)
А вообще, чтобы не забивать себе голову мильёном меток, я обычно делаю тупо и в лоб.
res1_t *res1 = NULL;
res2_t *res2 = NULL;
/* Здесь могут быть получены некоторые ресурсы, и отнюдь не обязательно все.
   Это нормально, просто тот же xmlGetNoNsProp (получить значение атрибута XML-элемента)
   вернёт NULL, если такой атрибут не указан (что в моём случае не ошибка).
 */
/* ... */
if (!cond_1)
  goto done;
if (!cond_2)
 goto done;
/* ... */
done:
if (res1) 
  free_res1(res1);
if (res2)
  free_res2(res2);
Edited 2012-04-21 20:08 (UTC)

[identity profile] metaclass.livejournal.com 2012-04-21 08:11 pm (UTC)(link)
О, вот так и надо.

[identity profile] sbj-ss.livejournal.com 2012-04-21 08:21 pm (UTC)(link)
Ребе, есть одна проблема. Помните, почему во времена царя Ирода Бейсика было хорошим тоном нумеровать команды с шагом в 10? :)
Тут то же самое. Если при доработке кода между вызовами func1 и func2 потребуется делать вызов func3, то придётся либо делать stage вещественным числом (и до 1.0625 запросто дойдёт), либо запутывать себе голову.
Код такой имеет определённое право на жизнь, да, но хотя бы enum для stage объявить не мешает.
Edited 2012-04-21 20:28 (UTC)

[identity profile] swizard.livejournal.com 2012-04-21 08:21 pm (UTC)(link)
На этот случай есть asprintf

[identity profile] sbj-ss.livejournal.com 2012-04-21 08:27 pm (UTC)(link)
Кстати, ребе, открою страшную тайну. Я обычно НЕ проверяю успешность отработки malloc при размере выделяемой памяти грубо менее килобайта.
Понятно, что не есть это хороший тон, но это специфика работы. В частности, в нашем XML-языке при невозможности что-то сделать на выходе команды генерируется узел с текстом ошибки, но - опять же в "куче". Не хватило место под первый malloc() - так не хватит и на цепочку последующих. И если системе килобайта жалко, то она поставлена не то что на колени, а раком, и даже на логгер особо рассчитывать не приходится. Как бы ни было обидно.

[identity profile] swizard.livejournal.com 2012-04-21 08:27 pm (UTC)(link)
В принципе, относительно малой кровью в сях можно эмулировать RAII через псевдо-замыкания и обёртки а-ля схема "call-with-open-file", "call-with-db-query", etc.

Другое дело, что процесс это достаточно унылый: в силу бедности языка приходится писать много лишнего кода, и контекст восприятия рвется. Но ничего, для сельской местности сойдёт, по мне всяко лучше, чем goto cleanup :)

[identity profile] theiced.livejournal.com 2012-04-21 08:27 pm (UTC)(link)
маккрос-проверяльщик и goto finally ;]

[identity profile] sbj-ss.livejournal.com 2012-04-21 08:30 pm (UTC)(link)
Увы, в той же MSVS такой прелести нет (что, правда, не мешает написать обёртку).

[identity profile] blackyblack.livejournal.com 2012-04-21 08:31 pm (UTC)(link)
"принято ли делать хитрожопые макросы типа CHECKERROR(some_call(),"some_call failed") и из них вываливаться из программы при ошибках?"

В особо умных исходниках принято. Такой макрос называется ASSERT :)
Но вообще, в сях мудрить не стоит - чем проще, тем лучше. Поэтому, как правило, тупо проверяется каждое возвращаемое значение каждой функции.

"А, и это - принято ли в C заниматься конкатенацией строк по поводу и без повода? А то, скажем, я привык в простых программах особо не мудрить и при необходимости складывать строки, если нет явных требований к производительности. Например, какая-нибудь дурь типа генерации строковых команд из шаблонов и параметров - тупо поскладывал строки и вернул результат. А в С придется strcat использовать, буфера какие-то объявлять, память выделять, трястись за ее удаление или же писать результат прямо в выходной файл, что вообще tight coupling."

Принято, если нет явных требований к производительности :) А вот память выделять без явной необходимости не стоит. Все буфера выделяются статически или в стеке, соответственно и освобождать ничё не требуется. Размер выделяемого буфера, естественно, выбирается эмпирически. А что вы хотели, low level programming.

[identity profile] metaclass.livejournal.com 2012-04-21 08:31 pm (UTC)(link)
Ох блин, ну хоть в одном бы мануале про нее написали :)

[identity profile] metaclass.livejournal.com 2012-04-21 08:32 pm (UTC)(link)
Строки под ошибки рекомендуют выделять заранее:)

Page 1 of 6