LINUX.ORG.RU

Правильный try-except в Си


1

1

Сразу извиняюсь за глупые вопросы: я не программист, просто надо написать маленькую программу для себя.

Проблема такая. В процессе движения по коду постоянно открываются новые файлы и прочее, что нуждается в «закрытии». При этом постоянно нужно отслеживать ошибки. В итоге получается код типа такого:

...
if (!(f = fopen(...))) {
    return 1;
}
...
if (!(g = log_open(...))) {
    fclose(f);
    return 1;
}
...
if (!(h = door_open(...))) {
    log_close(g);
    fclose(f);
    return 1;
}
...
...
...
if (!(z = mind_open(...))) {
    ...
    door_close(h);
    log_close(g);
    fclose(f);
    return 1;
То есть со временем обработчик ошибки становится всё более жирным.

Как нормальные программисты это организуют? Не предлагать писать не на Си, пожалуйста.


Ответ на: комментарий от dn2010

> А не проще вместо goto засовывать безошибочный вариант в блок else {} ?

А куда девать ошибочный вариант? Изобрази, может, я не понимаю, что ты имеешь в виду.

tailgunner ★★★★★
()
Ответ на: комментарий от dn2010

ну так вопрос в том, что делать, если ошибок может быть несколько - твой вариант сводится к коду из топика

tailgunner ★★★★★
()

Я не программист, но чтобы не париться с этим, сделал так:

#define RETMACRO return
// макросы для работы с памятью
#define CUALLOC(var, size)		do{				\
	CUerr = cudaMalloc((void**)&var, size);		\
	if(CUERROR("CUDA: can't allocate memory")){	\
		RETMACRO;								\
}}while(0)
...

функция(параметры){
	#undef RETMACRO
	#define RETMACRO do{ ret = 0; goto free_all; }while(0)
...
	CUALLOC(параметры);
...
free_all:
	CUFREE(hough_d);
	CUFREE(ima_d);
	return ret;
	#undef RETMACRO
	#define RETMACRO return
}

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от tailgunner

> а guard'ы какие-нибудь, например, порой весьма и весьма повышают читаемость кода

Я не знаю, что ты называешь guard'ами, и причем тут возвраты из середины функции.

см. Правильный try-except в Си (комментарий)

shty ★★★★★
()
Ответ на: комментарий от tailgunner

Кусок кода из LDD3

int _ _init my_init_function(void)
{
    int err;

    /* registration takes a pointer and a name */
    err = register_this(ptr1, "skull");
    if (err) goto fail_this;
    err = register_that(ptr2, "skull");
    if (err) goto fail_that;
    err = register_those(ptr3, "skull");
    if (err) goto fail_those;

    return 0; /* success */

  fail_those: 
    unregister_that(ptr2, "skull");
  fail_that: 
    unregister_this(ptr1, "skull");
  fail_this: 
    return err; /* propagate the error */
 }
anonymous
()
Ответ на: комментарий от shty

> см. Правильный try-except в Си (комментарий)

Такой стиль может быть приемлимым только тогда, когда функция состоит исключительно из «guard'ов». В противном случае... я уже сказал про мудаков, да?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> тут надо заметить, что далеко не все функции занимаются выделением/освобождением ресурсов

Ну как бы в топике обсуждается try-finally, который только для этого и используется.

разговор шёл о multiple return, try/finally там был лишь в качестве лейтмотива

shty ★★★★★
()
Ответ на: комментарий от anonymous

> Кусок кода из LDD3

Спасибо, я читал LDD3 (и LDD2 тоже).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Такой стиль может быть приемлимым только тогда, когда функция состоит исключительно из «guard'ов».

обоснуй

В противном случае... я уже сказал про мудаков, да?

это вообще мимо кассы, мудаку дай кнопку «сделать хорошо», он и её сломает

shty ★★★★★
()
Ответ на: комментарий от shty

> разговор шёл о multiple return

Несколько точек выхода из функции я упомянул походя, и не ожидал, что здесь найдется столько авторов «множеств сишных проектов», прогулявших лекции начального курса программирования.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> разговор шёл о multiple return

Несколько точек выхода из функции я упомянул походя

ты высказал мнение, тебе намекнули про излишнюю категоричность оного, чего ты теперь школоту поминаешь?

shty ★★★★★
()
Ответ на: комментарий от shty

>> Такой стиль может быть приемлимым только тогда, когда функция состоит исключительно из «guard'ов».

обоснуй

Тебе нужно обосновывать, почему плохо прятать нелокальные переходы в макросы?

tailgunner ★★★★★
()

Я надеюсь, кстати, что здесь не вылазили люди, орущие, что goto используют только дураки?

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от tailgunner

А зачем нелокальные прятать? Прячем локальные - и все ОК.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от shty

> ты высказал мнение, тебе намекнули про излишнюю категоричность оного, чего ты теперь школоту поминаешь?

Я выражаю мысли, которые считаю нужными, в том тоне, который считаю подходящим. Если кто-то в целях психологической самозащиты (или по другим причинам) считает меня самого неуравновешенной школотой или просто троллем, я это переживу.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

>> Такой стиль может быть приемлимым только тогда, когда функция состоит исключительно из «guard'ов».

обоснуй

Тебе нужно обосновывать, почему плохо прятать нелокальные переходы в макросы?

после того как тебя попросили обосновать своё высказывание ты начинаешь переспрашивать что именно имелось в виду в изначальной фразе к которой ты апеллировал, я правильно понял ситуацию?

shty ★★★★★
()
Ответ на: комментарий от tailgunner

тогда почему я уже в 3 сообщениях подряд по этой теме не вижу обоснования?

мсье не хочет снизойти до смердов и рассказать им немного мудрости?

shty ★★★★★
()
Ответ на: комментарий от tailgunner

Я выражаю мысли, которые считаю нужными, в том тоне, который считаю подходящим.

о да, ты крут

shty ★★★★★
()
Ответ на: комментарий от shty

> тогда почему я уже в 3 сообщениях подряд по этой теме не вижу обоснования?

То, что функция должна иметь одну точку входа и одну точку выхода, объясняется в курсе структурного программирования; запрет на изменение даже локального control flow в макросах - совершенно обычная практика; всё это азы. Это как обычная гигиена, и я не собираюсь этому учить.

мсье не хочет снизойти до смердов

Показное самоуничижение - это те же дешевые понты.

tailgunner ★★★★★
()
Ответ на: комментарий от shty

>> Я выражаю мысли, которые считаю нужными, в том тоне, который считаю подходящим.

о да, ты крут

Твое мнение очень важно для меня.

tailgunner ★★★★★
()
Ответ на: комментарий от shty

>тогда почему я уже в 3 сообщениях подряд по этой теме не вижу обоснования?

Потому что define в том применении, который мы сейчас обсуждаем, - это своего рода заменитель функции, со своими аргументами и, возможно, возвращаемым значением. Нормальная функция без longjmp не может прыгнуть на два уровня вверх, это явное быдлокодерство

Есть общепринятые академические стандарты кодирования, которым обучают всех программистов, и которых надо придерживаться. Нарушая их, твой код путает других и увеличивает время на его понимание.

ttnl ★★★★★
()

о чем тут вообще говорить только goto потому что много вложенных блоков это ад который поистине провоцирует утечку ресурсов и усложняет чтение кода приходится прыгать постоянно к if'у глазами

в кодах всех ядер операционных систем используют goto а не много вложенных блоков я бы тоже юзал goto это правильно

вот вам слова из книги Брайан У. Керниган, Роб Пайк, «Практика программирования»

Последовательность вложенных выражений if —. предвестник трудно читаемого кода, если не заведомых ошибок:

if (argc == 3)
	if ((fin = fopen(argv[1], "r"")) != NULL)
		if ((fout = fopen(argv[2], "w")) != NULL) {
			while ((c = getc(fin)) != EOF)
				putc(c, fout);
			fclose(fin); fclose(fout);
		} else
			printf("не открыть выходной файл %s\n", argv[2]);
	else
		printf("не открыть входной файл %s\n", argv[1]);
else
	printf("использование: cp входной_файл выходной_файл\n");
Последовательность условных операторов заставляет нас напрягаться, запоминая, какие тесты в каком порядке следуют, с тем чтобы в нужной точке вставить соответствующее событие (если мы еще в состоянии его вспомнить). Там, где должно быть произведено хотя бы одно действие, лучше использовать else if. Изменение порядка, в котором производятся проверки, ведет к тому, что код становится более понятным, кроме того, мы избавляемся от утечки ресурса, которая присутствовала в первой версии (файлы остались незакрытыми):
if (argc != 3)
	printf("использование: cp входной_файл выходной_файл\n");
else if ((fin = fopen(argv[1], "r"")) == NULL)
	printf("не открыть выходной файл %s\n", argv[2]);
else if ((fout = fopen(argv[2], "w")) != NULL) {
	printf("не открыть выходной файл %s\n", argv[2]);
	fclose(fin);
} else {
	while ((c = getc(fin)) != EOF)
		putc(c, fout);
	fclose(fin);
	fclose(fout);
}
Мы производим проверки до тех пор, пока не находим первое выполненное условие, совершаем соответствующее действие и опускаем все последующие проверки. Правило тут такое: каждое действие должно быть расположено как можно ближе к той проверке, с которой связано. Другими словами, каждый раз, произведя проверку, совершите что-нибудь.

anonymous
()

Используй goto. Куча примеров в коде ядра Linux.

Deleted
()
Ответ на: комментарий от tailgunner

То, что функция должна иметь одну точку входа и одну точку выхода, объясняется в курсе структурного программирования;

конечно-конечно, только вот то что наивно следовать один раз «забОтаненому» правилу невзирая на обстоятельства - это уже выясняется потом, при столкновении с реальным миром

запрет на изменение даже локального control flow в макросах - совершенно обычная практика;

ты такой суровый детка, ммм

открой для себя, например, google test и google mock и посмотри уже как работают люди, которым не мешает демагогия пуритан от software design

и открой uthash какой-нибудь, и снова удивись

всё это азы.

вазы, тазы, камазы, белазы...

> мсье не хочет снизойти до смердов

Показное самоуничижение - это те же дешевые понты.

ну да, а изображать Чака Норриса - это дорогие понты и Ъ :)

//походу всё же пора запилить тег sarcasm

shty ★★★★★
()
Ответ на: комментарий от ttnl

Потому что define в том применении, который мы сейчас обсуждаем, - это своего рода заменитель функции, со своими аргументами и, возможно, возвращаемым значением.

давай-ка уточним:
1) мы не обсуждаем #define как таковой, мы обсуждаем guard clause и single exit point
2) #define - не заменитель функции

Нормальная функция без longjmp не может прыгнуть на два уровня вверх, это явное быдлокодерство

не понял, поясните Вашу мысль

Есть общепринятые академические стандарты кодирования, которым обучают всех программистов, и которых надо придерживаться.

программистов, вообще-то учат, по-крайней мере должны учить, решать задачу эффективным образом, а не «академические стандарты кодирования» зубрить

Нарушая их, твой код путает других и увеличивает время на его понимание.

как я уже говорил, рассуждать вот так не имея конкретной ситуации в виду - наивно

shty ★★★★★
()

Про LDD тут уже намекали, в самом деле, почему бы автору не загялнуть в этот самый LDD и узнать, как легко и без мук совести можно применить оператор goto для имитации try{}catch(){}.

gandjubas
()
Ответ на: комментарий от anonymous

> скоро придет понимание что лучше явно все писать а не прятать за макросами

Макросы в С необходимы. Они позволяют увеличить читабельность кода и сократить его количество, если написаны правильно.

gandjubas
()
Ответ на: комментарий от tailgunner

> goto

Отметил для себя этот вариант, как самый легко переписываемый.
Как и объявление всех переменных, освобождение ресурсов должно быть в одном месте.

backbone ★★★★★
()
Ответ на: комментарий от gandjubas

я про маросы навроде #define F(e) if (!(e)) return false я юзал их раньше думал что удобно потом стал явно писать поумнел наверное я не против обычных макросов типа max min

anonymous
()
Ответ на: комментарий от toady2

goto рушит всю оптимизацию компилятора.

Ыыыы... Оптимизации компилятора в ситуации, когда идет работа с I/O и сваливается из-за ошибок I/O??? Да у вас проблемы с оценкой узких мест, батенька :-)

no-dashi ★★★★★
()
Ответ на: комментарий от shty

мы обсуждаем

Мы сейчас обсуждаем следующее:

Тебе нужно обосновывать, почему плохо прятать нелокальные переходы в макросы?

Нормальная функция без longjmp не может прыгнуть на два уровня вверх, это явное быдлокодерство

не понял, поясните Вашу мысль

int f ()
{    ...
     c();
     ...
}

Это когда делая return в c, ты выходишь из f().

программистов, вообще-то учат, по-крайней мере должны учить, решать задачу эффективным образом, а не «академические стандарты кодирования» зубрить

Чтобы эффективно решать задачи, код должен быть написан понятно для других. На понимание его синтаксиса не должно уходить много времени.

Есть канонические правила, которых все придерживаются. Нет ни одного нормального OS проекта, где макросы используются по-твоему. Ты споришь с аксиомами, а это неправильно.

ttnl ★★★★★
()
Ответ на: комментарий от ttnl

Мы сейчас обсуждаем следующее:

Тебе нужно обосновывать, почему плохо прятать нелокальные переходы в макросы?


с каких это пор риторические вопросы требуют обсуждения?

>>Нормальная функция без longjmp не может прыгнуть на два уровня вверх, это явное быдлокодерство

не понял, поясните Вашу мысль

int f ()
{    ...
     c();
     ...
}

Это когда делая return в c, ты выходишь из f().

1. мсье не знает разницы между вызовом функции и макросом?
2. причём тут быдлокодерство?
3. а с longjump может прыгнуть, и что дальше?

shty ★★★★★
()
Ответ на: комментарий от ttnl

Чтобы эффективно решать задачи, код должен быть написан понятно для других. На понимание его синтаксиса не должно уходить много времени.

конечно, и, если в команде принять писать одним способом, а ты приходишь с другим то, прости, но это идиотия, идиотия не меньшая, чем переписывать огромный проект с нуля из-за того что у кого-то зачесалась codestyle-nazi-пятка

кто-то из великих сказал: самый лучший <whatever> - это тот, который подходит вашей команде

люди очень по-разному пишут проекты, и нет единого такого стиля, *surprise*?

shty ★★★★★
()

распилить


...
if (!(f = fopen(...))) {
    return 1;
}
...
if (!(g = log_open(...))) {
    fclose(f);
    return 1;
}
...
if (!(h = door_open(...))) {
    log_close(g);
    fclose(f);
    return 1;
}
...
...
...
if (!(z = mind_open(...))) {
    ...
    door_close(h);
    log_close(g);
    fclose(f);
    return 1

В треде слишком много спорили, но не о тех вещах, которые тут действительно нужны. Выходить из функции через goto плохо, но и делать много точек выхода тоже плохо! Надо оставить в методе минимальное количество точек выхода. В каждом методе.

int main() {
    ...
    if (!(f = fopen(...))) {
        result = do_something_with(f);
        fclose(f);
        return result;
    }
    ...
}
//-------------------------------------------------
int do_something_with(f) {
    ...
    if(!(g = log_open(...))) {
        result = do_something_else_with(f, g);
        log_close(g);
        return result;
    }
    ...
}
//-------------------------------------------------
int do_something_else_with(f, g) {
    ...
    if(!(h = door_open(...))) {
        result = do_something_else_more_with(f, g, h);
        door_close(h);
        return result;
    }
    ...
}
//-------------------------------------------------
int do_something_else_more_with(f, g, h) {
    if(!(z = mind_open(...))) {
        ...
        return 1;
    }
    ...
}
....

В таком варианте у тебя контекст (открыл-закрыл ресурс) не размазывается по коду, а сконцентрирован в единственном месте. Пропадает дублирование, нет необходимости в goto, улучшается читаемость и падает ошибкоёмкость кода.

Впрочем, если внимательно вглядываться в код, то можно заметить два последствия рефакторинга. Возрастает количество функций и количество аргументов у них. Первое последствие - абсолютно не проблема, а второе может стать проблемой, если таких ресурсов много. Но на такой случай можно применить приём под названием preserve whole object: создать структуру с указателями на ресурсы и передавать в функции только один экземпляр этой структуры, а не пачку разрозненных ресурсов.

BTW, я немного не понял код. Тут действительно единица возвращается, когда всё отработало нормально? Или я забыл С, или там для таких случаев таки используется 0

Zloddey
()
Ответ на: распилить от Zloddey

немного не понял код

OMG, меня проглючило. Вместо

    if (!(f = fopen(...))) {
        result = do_something_with(f);
        fclose(f);
        return result;
    }

Следует читать

    if (!(f = fopen(...))) {
        return 1;
    }
    result = do_something_with(f);
    fclose(f);
    return result;
Далее тоже аналогично.

Сначала ведь правильно написал, потом сглюканул

Zloddey
()
Ответ на: комментарий от Zloddey

так мы никогда ничего ненапишем одна вода слишком все раздроблено задача расплылась на несколько функций и только ухудшает понимание

anonymous
()
Ответ на: комментарий от anonymous

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

Zloddey
()
Ответ на: комментарий от Zloddey

> А причём здесь C#?

Твой preserve whole object ссыслается на C#, Или это Java? В любом случае, это не Си. И в Java, и в C# есть нормальный try-except-finally

tailgunner ★★★★★
()

не в тему, но в топик

раз пошла такая пьянка, то вот вам кошерный try-catch на ansi c

#include <err.h>
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>

static  sigjmp_buf exception;

#define try     if (!sigsetjmp(exception, 1))
#define catch   else
#define throw   siglongjmp(exception, 1)

void
handler(int sig)
{
        throw;
}

void
init_try(void)
{
        struct sigaction sa;

        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sa.sa_handler = handler;
        sigaction(SIGFPE, &sa, NULL);
        sigaction(SIGSEGV, &sa, NULL);
}

int
main()
{
        int n = 0;

        init_try();

        try
                printf("%d\n", 1/n);
        catch
                warnx("Look Ma, I divided by zero!");

        try
                ((void (*)(void))NULL)();
        catch
                warnx("SIGSEV you say? Well...");

        return 0;
}

beastie ★★★★★
()
Ответ на: комментарий от tailgunner

>Твой preserve whole object ссыслается на C#

А, так это просто была первая рабочая ссылка из гугла по данной фразе. Я даже не посмотрел, на каком языке оно написано. Просто показал сам принцип, а детали возможной реализации в С описал после сцылки

Zloddey
()
Ответ на: не в тему, но в топик от beastie

Это некошерный try-catch (и не на ansi c):

нельзя вложить несколько блоков try друг в друга;

после throw переменные могут сойти с ума, если не объявить их как volatile.

Первую проблему можно решить, вторую - нет.

unsigned ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.