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] 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] familom.livejournal.com 2012-04-21 07:39 pm (UTC)(link)
Возврат ресурсов при обработке — через метки и goto. Например, goto failed_config_opening, goto success и т.п.

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

[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] berezovsky.livejournal.com 2012-04-21 08:57 pm (UTC)(link)
внезапно си

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

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

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

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

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

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

[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] 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] 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;
}