Как стать автором
Обновить

Комментарии 50

А имеет ли смысл использование подобного кода в языке C учитывая, что там нет деструкторов? Ведь мы получим утечки ресурсов, а это не всегда допустимо.
Почему-то мне кажется, что хоть код и менее захламлен различного рода ветвлениями(if'ами), но все равно читабельность падает, так как человеку, читающему код, придется лишний раз поломать голову, что же вы такое тут делаете.
Скажем так, когда все в отделе пишут так, чтение проблем не вызывает. Такую методику же не от хорошей жизни придумали, а чтобы отладку себе упростить. Относится к нему надо как к просто ещё одному паттерну.
Вот как бы выглядел «деструктор» для этой подсистемы:

// деинициализация подсистемы
void subsystem_destroy(subsystem* self) {
  filter2_destroy(&self->f2);
  filter1_destroy(&self->f1);

  free(self->buff_size2);
  free(self->buff_size1);
  self->buff_size2 = self->buff_size1 = NULL;

  sema_destroy(&self->sema3);
  sema_destroy(&self->sema2);
  sema_destroy(&self->sema1);
  self->sema3 = self->sema2 = self->sema1 = SEMA_INVALID_HANDLE;
}


Его обязана вызывать вышележащая функция при обнаружении ошибки или в конце работы при нормальной завершении.

«Объект» конструируется в два этапа — сначала создаётся «конструктором» вида
bool subsystem_construct(err_info* e);
При этом под него выделяется память, а все внутренние поля инициализируются значением «неинициализировано». Т.е. указатели — в NULL, семафоры — в INVALID_HANDLE и т.п. А лишь затем — уже функцией subsystem_init. Соответственно, для этого объекта допустимо вызывать его «деструктор» где бы не прервался нормальный ход его инициализации.

Писать код так, чтобы он был «leak proof» — это отдельные техники и приёмы, они с обработкой ошибок связаны, но не сильно.
Подождите-подождите, а ведь идентификатор можно получить тупо взяв указатель на строковую константу (__FILE__ __LINE__). Её уже можно сразу и в UART выпулить, если вдруг чего, да и в обработчике аппаратной исключительной ситуации глянуть в него можно, если уж hardfault схлопотали.
Нет, это неудобно оказалось. Хранить надо больше больше (дополнительный указатель на строку). Кроме того, имени файла и строки — недостаточно, надо ещё «версию файла» учитывать (в прошивке файл на несколько месяцев древнее современного, номера строк не совпадают). Да и зачем два идентификатора, если можно использовать один?

Кроме того, были системы, где совсем не было возможности выдавать строковый отладочный вывод. А несколько int'ов передать — была.
Вот именно, зачем два идентификатора? Только указатель на строку. Ну и где-то, естественно, хранить номер версии (либо $Id$, если это svn, либо же идентификатор среза любым другим образом (-D$(hg id), например)), ну и дату. А если Вы не пользуетесь системами контроля версий, я даже не знаю, что Вам посоветовать.
«Что только Русские не придумают, лишь бы дороги не делать» (С), тут аналогичная ситуация, что только не придумают, лишь бы на С++ не переходить. А чем он хуже? Учитывая тот факт, что если исключение не произошло, то нет никакого оверхеда в отличии от данного примера.
НЛО прилетело и опубликовало эту надпись здесь
Там написано «языком разработки является C или компилятор С++ для нашей платформы не поддерживает exceptions», что несколько отличается от увиденого вами «для систем, где C++ нет или компилятор C++ генерит промежуточный код на C», да и сабж прямо указывает на plain C.

Для AxisPod: Ещё есть места, где использование C++ не имеет особого смысла, например драйверы в Linux.
НЛО прилетело и опубликовало эту надпись здесь
А что удивительного в том, что цена исключения высокая, если его возникновение — ненормальная ситуация.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Ну эксепшены и не предназначены для нормального хода выполнения программы, а в случае ошибки можно и потратить ресурсы, не должны же они тысячами в секунду сыпаться.
НЛО прилетело и опубликовало эту надпись здесь
Если для вас нехватка памяти является штатной ситуацией — не бросайте в случае нехватки памяти исключение. Понятие то растяжимое, однако вы сами определяете, что для вас является исключительной ситуацией, а что нет.
НЛО прилетело и опубликовало эту надпись здесь
Вас, кстати, не смущает, что исключения оказываются то лучше кодов возврата (не нужно проверять), то хуже (очень дорогие)?

Меня не смущает. Потому что если выбросилось исключение — мне на производительность уже плевать.
А вам часто удавалось поработать, когда прилетали исключения о нехватки памяти? Просто про это говорят многие, но при этом сами с этим не сталкивались. В принципе очень сложно написать код, который будет адекватно работать в этой ситуации, ибо кинуть исключение это создать объект, а где его создать когда память закончилась? Опять же все зависит от ОС, от компилятора и т.д.
> ибо кинуть исключение это создать объект, а где его создать когда память закончилась?

Компилятор обязан резервировать память, чтобы ее хватило на такое исключение. И нехватка памяти совершенно нормальная ситуация, с которой сталкивается каждый программист, пишущий что-то более-менее сложное.
Ну что-то я не сталкивался ибо довольно жестко контролирую этот процесс, а компилятор не всегда сможет вам зарезервировать память, ибо ваш процесс не единственный в системе.
Компилятор обязан это сделать, это требование стандарта языка C++. Ведь в комитете стандартизации не дураки сидят и понимают, что для генерации исключения требуется память.
Компилятор зарезервирует место в случае, если памяти в ОС достаточно, но на процесс уже выделить нельзя, в этом случае без проблем. Но как по вашему должна резервироваться память, если в системе куча процессов, которые просят себе память, как компилятор должен зарезервировать память? Разве что заранее выделить какой-то здоровый кусок, ибо неизвестно сколько потребуется памяти в итоге. А это уже больше на костыль похоже, нежели на нормальную реализацию. И как быть в ситуации встраиваемых систем, где каждый байт на счету?

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

Да и в реально жизни приложения от ведущих разработчиков при нехватки памяти так же тупо падают и все.
Почему неизвестно? Памяти должно быть достаточно для создания bad_alloc и все. И конечно же обработка такого исключения не должна приводить к очередной попытке выделения памяти, зато можно попытаться ее освободить. И естественно, что резервирование осуществляется при запуске, а не в момент нехватки памяти.

Какой смысл было бы вводить исключения, если его нельзя будет поймать и обработать? Ведь это затем и придумано. И еще раз повторяю, что там сидят не дураки, а опытные программисты.

В реальной жизни к приложениям бывают разные требования. Иногда одно из них — устойчивость к ошибкам. Пусть не получится выполнить нужную функцию из-за нехватки памяти, но падать при этом нельзя.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
unwind protect'а я что-то не вижу, «ни одного явного if» воспринял как шутку, семантика проброса исключений отсутствует. Итого, причем тут исключения — я так и не понял. Монада Maybe это Тип, причем он тут — я тоже не понял :]

А по стилю — стиль хороший :) Только все в невложенный && не засунешь. Разве что в простых случаях
Тут возвращаемое значение всех функций — или какой-то результат, или сигнал о том, что результат отсутствует. Самое что ни есть Just a | Nothing. Вызовы функций удобным способом объединяются, при возникновении Nothing последующие функции уже не вызываются — та же логика, что и у bind для этой монады. Мне кажется, это очевидно.

Аналогично, все эти subsystem_init, subsystem_destroy, принимающие указатель на структуру subsystem — это же типичный ООП, просто на C.
Относительно Just.

Тут обрезана область значения функции. Если у тебя будет
f :: X -> X, как ты будешь извещать об ошибке? Я бы понял, если бы было написано нечто вроде

#define MAYBE(x) struct { x value; int nothing; }

итп, или же был бы отражен(раскрыт) анализ e->stack.

Поинт относительно ООП комментировать не буду
здесь «int nothing» соответствует bool, который функция возвращает, а «x value» — то, что функция возвращает через свои параметры. Монада Maybe здесь своими свойствами проявляется, а не наличием/отсутствием букв «m a y b e» в названиях.
У тебя есть
float div(int a, int b)

Как возвращать по твоей схеме эксепшн, если b = 0?
bool div(int a, int b, int* res);
Вернее, bool div(int a, int b, float* res);
Ок, аргументы принимаются.
Но моё очень личное мнение по этому поводу — это как называть велосипед автомобилем, потому что он ездит :)
Вообще есть еще механизм SEH ( __try { } __except(..) { } ), который реализуется немного похоже, но там «чистые» исключения. В т.ч. аппаратные
Подумаешь, что SEH — это только винда.
НЛО прилетело и опубликовало эту надпись здесь
Itanium C++ ABI: Exception Handling
Описанная в доке инфраструктура без проблем может использоваться в чистом Си, и портировать ее на платформу без компилятора С++ предположительно не составит проблем.
Если уж вы решили писать в том числе и про plain С, то хотя бы не используйте boolean. Его, конечно, несложно заменить, но глаз режет.
Да я думал, что если напишу bool, то будут говорить, что это не на С написано.
C99.
Согласен с коментом. Я ждал каких то механизмов для действительного изменения порядка выполнения, возможно через сетжамп/лонгжамп, или без них (хотя и не представляю как — но, думаю было бы интересно).
НЛО прилетело и опубликовало эту надпись здесь
Так ведь можно же и без них нужный результат получить. Задача была не «перенести C++ exceptions один-в-один в язык C», а сделать так, чтобы любая функция могла сигнализировать об ошибке, которая может быть обработана только на каком-то из верхних уровней. В C++ для этого exceptions используют, но это просто один из инструментов, их много разных.
Если одну задачу можно решить двумя способами, то зачем тогда первому решению давать название второго? Ну ладно, если дали — но это как раз и сбило с толку.

Хотя если честно (это немножко подумав), я забираю слова назад: я ожидал изменения порядка выполнения, потому что С++-исключения его меняют, а в идеале надо было абстрагироваться от известных реализаций исключений.

*теперь торт!!!
з.ы. вот видите, иногда очень полезно поспрашивать после лекции.
А почему не используете goto для освобождения ресурсов? Это же true C way.
Я не понял, где тут исключения? Простая обработка ошибок, по типу логирования.
К тому же любом в дебаггере сохраняется call-stack. А в вашем случае даже не стек, а последовательность вызовов. Близко не похоже на тот как раскручивается стек при throw в плюсах.
«Исключения» — это возникновение «исключительной ситуации» (памяти не хватило, параметры невалидны, и т.п.). Они тут есть. И чтобы не повторяться — habrahabr.ru/post/141507/#comment_4735235

А вот возможности залезть дебаггером зачастую нет.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории