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)
финализеру нужно подчищать все, что успешно отработало ДО него.

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 19:12 (UTC) - Expand

[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] 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 :)

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 19:46 (UTC) - Expand
(deleted comment)

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:21 (UTC) - Expand
(deleted comment)

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:39 (UTC) - Expand
(deleted comment)
(deleted comment)

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 21:45 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 21:33 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 20:35 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:40 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 20:46 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 20:48 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 20:49 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 20:51 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 20:52 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 20:57 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:00 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:01 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:09 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:11 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:13 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:18 (UTC) - Expand

(no subject)

[identity profile] dair-spb.livejournal.com - 2012-04-22 08:55 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-22 09:08 (UTC) - Expand

(no subject)

[identity profile] max630.livejournal.com - 2012-04-22 04:14 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 21:02 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:05 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:06 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:09 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 21:18 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:19 (UTC) - Expand

(no subject)

[identity profile] thedeemon.livejournal.com - 2012-04-22 05:31 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 21:07 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:10 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 21:17 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:35 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 20:54 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 20:55 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 21:00 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:02 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 21:06 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 21:01 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:04 (UTC) - Expand

(no subject)

[identity profile] berezovsky.livejournal.com - 2012-04-21 21:06 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-21 21:08 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:05 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 20:11 (UTC) - Expand

(no subject)

[identity profile] blackyblack.livejournal.com - 2012-04-21 20:39 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 20:45 (UTC) - Expand

(no subject)

[identity profile] blackyblack.livejournal.com - 2012-04-22 06:35 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:45 (UTC) - Expand

(no subject)

[identity profile] vp.livejournal.com - 2012-04-21 20:48 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-22 12:33 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-22 14:23 (UTC) - Expand

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

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

(no subject)

[identity profile] swizard.livejournal.com - 2012-04-21 20:21 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:30 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 20:31 (UTC) - Expand

(no subject)

[identity profile] swizard.livejournal.com - 2012-04-21 20:36 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:46 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 19:38 (UTC) - Expand

(no subject)

[identity profile] blackyblack.livejournal.com - 2012-04-21 20:35 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:41 (UTC) - Expand

[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: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:27 pm (UTC)(link)
Кстати, ребе, открою страшную тайну. Я обычно НЕ проверяю успешность отработки malloc при размере выделяемой памяти грубо менее килобайта.
Понятно, что не есть это хороший тон, но это специфика работы. В частности, в нашем XML-языке при невозможности что-то сделать на выходе команды генерируется узел с текстом ошибки, но - опять же в "куче". Не хватило место под первый malloc() - так не хватит и на цепочку последующих. И если системе килобайта жалко, то она поставлена не то что на колени, а раком, и даже на логгер особо рассчитывать не приходится. Как бы ни было обидно.

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

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 20:43 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-21 20:49 (UTC) - Expand

(no subject)

[identity profile] justy-tylor.livejournal.com - 2012-04-21 21:05 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 21:07 (UTC) - Expand

(no subject)

[identity profile] justy-tylor.livejournal.com - 2012-04-21 21:22 (UTC) - Expand

(no subject)

[identity profile] sbj-ss.livejournal.com - 2012-04-21 21:36 (UTC) - Expand

[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] 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:41 pm (UTC)(link)
>Все буфера выделяются статически или в стеке
Если проверять при этом размеры, то наверно, ничего страшного. Надо себе сломать мозг и делать универсально только там где это действительно нужно, типа "а тут нашей проге через stdin дадут 2гб строку из 20 гб файла"

[identity profile] permea-kra.livejournal.com 2012-04-21 08:39 pm (UTC)(link)
>А, и это - принято ли в C заниматься конкатенацией строк по поводу и без повода?
Емнип, там трясуться за производительность, поэтому в линупсах даже придумали writev. Вот куда он потом дополз - вопрос более интересный.

[identity profile] metaclass.livejournal.com 2012-04-21 08:44 pm (UTC)(link)
scatter-gather. какой гамон.

(no subject)

[identity profile] permea-kra.livejournal.com - 2012-04-22 04:50 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2012-04-22 07:10 (UTC) - Expand

[identity profile] plumqqz.livejournal.com 2012-04-21 08:55 pm (UTC)(link)
В дельфи/java/C#/Clojure это всегда делается через обработку исключений в виде try-finally/using или чего-то аналогичного, в C++ - RAII, а вот что делать в С?

Ну и тут делайте. setjmp/longjmp

[identity profile] esil0x.livejournal.com 2012-04-22 01:18 am (UTC)(link)
setjmp/longjmp уже давно нигде не используется для исключений.

(no subject)

[identity profile] plumqqz.livejournal.com - 2012-04-22 19:29 (UTC) - Expand

[identity profile] berezovsky.livejournal.com 2012-04-21 08:57 pm (UTC)(link)
внезапно си

[identity profile] astoon.livejournal.com 2012-04-21 09:39 pm (UTC)(link)
Спасибо что живой.

(no subject)

[identity profile] berezovsky.livejournal.com - 2012-04-21 21:41 (UTC) - Expand

(no subject)

[identity profile] blueher.livejournal.com - 2012-04-22 20:49 (UTC) - Expand

[identity profile] kong-en-ge.livejournal.com 2012-04-21 09:36 pm (UTC)(link)
сколько интересных способов залезть на шкаф...

[identity profile] metaclass.livejournal.com 2012-04-22 07:04 am (UTC)(link)
Ребе, я теперь окончательно понял, почему ситиинфы под линукс не бывает :)

(no subject)

[identity profile] kong-en-ge.livejournal.com - 2012-04-25 11:18 (UTC) - Expand

[identity profile] http://users.livejournal.com/mak_/ 2012-04-21 10:24 pm (UTC)(link)
сто лет уже не программировал:) assert не поможет?

[identity profile] sbj-ss.livejournal.com 2012-04-21 10:40 pm (UTC)(link)
Религия не позволяет считать это чем-то бОльшим, чем средство отладки :)
Да и вывести "can't lock file "c:\beetles and frogs.txt", function CrazyClownFunction" assert не даст. А вот юзеры конечной программы вылетающее окошко "Assertion failed, program matzo.exe line 1488" дико ненавидят.

[identity profile] qehgt.livejournal.com 2012-04-21 11:52 pm (UTC)(link)
Мы когда-то делали так:
http://habrahabr.ru/post/141507/

[identity profile] theiced.livejournal.com 2012-04-22 01:24 am (UTC)(link)
это же ссылка на хабр!!112

[identity profile] baramin.livejournal.com 2012-04-22 12:52 am (UTC)(link)
В нашем проекте я законодательно запретил С. Ибо, если если есть С, есть и С++. Иначе - нефиг.
Ну, если лезть обратно на пальму, то
1]
status = 0;
while (1) {
if ( fail(status = f1()) )
break;
if ( fail(status = f2()) )
break;
status = f3();
break;
}
//зачищаем всех, кто не NULL

2] лестница (MS COM approach)
var lhv1;
status = create1(&lhv1);
if (fail(status)) {
print_error(status);
} else {
//хитрость в том, что dispose пишется сразу.
//статус заверщения - глобален! Привет HRESULT
//здесь в том же формате пишем второй шаг (рекурсия:) ):
var lhv2;
status = create2(&lhv2);
if (fail(status)) {
print_error(status);
} else {
//next step is here
dispose(lhv2)
}

dispose(lhv1)
}
return status.

[identity profile] metaclass.livejournal.com 2012-04-22 07:07 am (UTC)(link)
Торвальдс воспрещает С++ :)
Впрочем, у меня как раз наверно и на С++ можно было бы все сделать и было бы проще.

(no subject)

[identity profile] justy-tylor.livejournal.com - 2012-04-22 08:47 (UTC) - Expand

(no subject)

[identity profile] esil0x.livejournal.com - 2012-04-22 09:14 (UTC) - Expand

(no subject)

[identity profile] justy-tylor.livejournal.com - 2012-04-22 09:40 (UTC) - Expand

(no subject)

[identity profile] esil0x.livejournal.com - 2012-04-22 09:42 (UTC) - Expand

(no subject)

[identity profile] justy-tylor.livejournal.com - 2012-04-22 09:55 (UTC) - Expand

(no subject)

[identity profile] esil0x.livejournal.com - 2012-04-22 09:59 (UTC) - Expand

(no subject)

[identity profile] justy-tylor.livejournal.com - 2012-04-22 10:21 (UTC) - Expand

(no subject)

[identity profile] esil0x.livejournal.com - 2012-04-22 10:23 (UTC) - Expand

(no subject)

[identity profile] blackyblack.livejournal.com - 2012-04-22 10:13 (UTC) - Expand

[identity profile] esil0x.livejournal.com 2012-04-22 01:20 am (UTC)(link)
Есть один правоверный способ грамотной обработки ошибок в программах на С, применение которого возможно в 99% случаев. Последовательность действий следующая:

1) Переименовать исходники в *.cpp
2) use raii
3) PROFIT!!!

С конкатенацией строк, говорят, тоже помогает.

[identity profile] theiced.livejournal.com 2012-04-22 01:25 am (UTC)(link)
это проверенный способ сделать вместо программы кусок говна.

(no subject)

[identity profile] esil0x.livejournal.com - 2012-04-22 01:30 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2012-04-22 11:22 (UTC) - Expand

(no subject)

[identity profile] esil0x.livejournal.com - 2012-04-22 11:37 (UTC) - Expand

[identity profile] esil0x.livejournal.com 2012-04-22 01:50 am (UTC)(link)
А, да, тут не упомянули ещё один вариант:

resource1 = alloc_resource1();
if (resource1) {
resource2 = alloc_resource2();
if (resource2) {

// use resource1 & resource2

free_resource2(resource2);
} else
log("alloc_resource2 failed");

free_resource1(resource1);
} else
log("alloc_resource1 failed");


Единственный большой недостаток - с увеличением количества ресурсов растёт вложенность. Иногда помогает разбиение на более мелкие функции.
Но лучше перестать грызть себе нокти и делать так, как я написал в предыдущем комменте.

[identity profile] sleepy-drago.livejournal.com 2012-04-22 07:10 am (UTC)(link)
зависит от степени лени девелопера.
долбо^W можно пользоваться макросом который прыгает за return success где в конце функции в обратном порядке написаны освобождалки ресурсов в нужное место.
Можно иметь и список функций очистки на функцию и вызывать их в обратном порядке. Можно иметь класс (тссс!) строки и конкатенатор к нему. И память выделяемую регионами так чтобы ни одного free в коде не было.
Посмотрите на код тех для кого вы пишете и делайте так же как они - иначе они не примут контрибуции :) Я ж не верю что это себе :D

[identity profile] metaclass.livejournal.com 2012-04-22 07:11 am (UTC)(link)
Себе, себе пишу. Для кого-то я не настолько хорошо С умею.

[identity profile] dair-spb.livejournal.com 2012-04-22 09:03 am (UTC)(link)
Я, в результате подобных же мозговых нестыковок, дошёл до такого:

 b r

     res  allocSomething2
     res  
    
        
        cres
        releaseSomething2
    


 a

     res  allocSomething1
     res  
    
        
        bres
        releaseSomething1
    

Edited 2012-04-22 09:03 (UTC)

[identity profile] berezovsky.livejournal.com 2012-04-22 09:08 am (UTC)(link)
ээээ, как разукрашечку сделать?

(no subject)

[identity profile] dair-spb.livejournal.com - 2012-04-22 09:48 (UTC) - Expand

[identity profile] fkng-stupid-lj.livejournal.com 2012-04-22 11:29 am (UTC)(link)
foo_t* 
foo(void)
{
    bar_t *bar;
    baz_t *baz;
    foo_t *foo;

    bar = bar_new();
    if (!bar)
      goto e0;
    baz = baz_new();
    if (!baz)
      goto e1;
    foo = foo_new(bar, baz);
    if (!foo)
      goto e2;
    return foo;

e2: baz_free(baz);
e1: bar_free(bar);
e0: return NULL;
}


Дешево и сердито. Плюс вариации. Можно, конечно, и exceptions замутить (типа таких (http://www.on-time.com/ddj0011.htm)), но в сборном проекте они не особенно полезны, вот если монолитная система, тогда может быть.

Насчет строк -- или, как сказали, aprintf, если есть, или поискать какую-нибудь более развесистую библиотеку.

[identity profile] fillest.livejournal.com 2012-04-22 02:39 pm (UTC)(link)
по поводу строк - http://bstring.sourceforge.net/

[identity profile] chemodax.livejournal.com 2012-04-22 05:07 pm (UTC)(link)
Для многих вещей C лучше чем C++, просто его нужно уметь готовить. APR с пулами, и что-нить типа svn_error_t для возврата ошибок.

[identity profile] http://users.livejournal.com/_slw/ 2012-04-22 07:53 pm (UTC)(link)
почитал каментики. бля.

ресурсы освобождать очень просто:

return OK;
fail:
if(res1) free_res(res1);
и так по каждому;
return FAIL;
}

[identity profile] metaclass.livejournal.com 2012-04-22 08:11 pm (UTC)(link)
Так и сделал в итоге.