Comments 24
В качестве тренировки для ума пойдёт, но в принципе в языке C есть встроенные средства типа сигналов. Которые делают то же самое, но на уровне библиотеки C.
Вот первый попавшийся в выдаче Google сайт.
https://www.geeksforgeeks.org/signals-c-language/
Я не C, а C++ программист и может быть поэтому не понимаю, но как использовать сигналы вместо исключений? Для взаимодействия с другими процессами - понятно, но что толку слать сигнал самому себе? Стек после хэндлера будет тем же, выполнение продолжится со следующей после kill строки. Зачем?
Никак не использовать. Это очень странная идея
Механизм, очень близкий к механизму в C есть --- setjmp()/longjmp(), заголовочный файл --- setjmp.h, стандартные библиотеки C. Кое-что приходится делать руками, например, закрывать файлы и освобождать память, но это работает. Как-то довелось использовать библиотеку для работы с PNG и вот там эти джампы активно используются и как раз для обработки ошибок.
Сигналы не дают локальности исключений. Хотелось, чтобы как в других языках, можно было делать try-catch блоки внутри других try-catch блоков. Плюс сигналы не дают возможности удобно пробрасывать данные. В любом случае писать свою обёртку, но так нельзя всё испортить извне, например, навесив другой обработчик на этот же сигнал или вызвав setjmp где-то в коде (их предлагали использовать ниже)
setjmp() работает довольно неплохо, но требует несколько большей внимательности к деталям, иначе могут начать течь ресурсы. Ваш вариант безопаснее, но и несколько более запутанный. В общем, очень неплох в качестве дополнения к стандартному способу, но в качестве полной замены я бы его не рассматривал.
Сигналы - это средство ОС Linux, а не языка С. На Windows тот же самый код работать не будет.
Кроме того, отправка сигнала - это системный вызов, а это долго - переключение контекста, сохранение регистров, работа ядра и так далее.
Да это всё фигня по сравнению с тем, что после обработки сигнала процесс возвращается в ту же точку, из которой его выдернули, что логично, учитывая, что сигнал --- это механизм общения процессов и сторонний процесс не может знать, в какой точке кода находится вызываемая сторона. А вот исключение C++ и других объектно-ориентированных языков, предложенный вариант и setjmp()/lonhjmp() как раз выбрасывают процесс в заданную точку, "разматывая" стэк, то есть в точку сбоя процесс уже не вернётся. В варианте setjmp()/lonhjmp() надо ещё вручную освободить ресурсы, но автоматизировать этот процесс в C невозможно.
Сигналы используются для межпроцессного взаимодействия, а для обработки исключений лучше посмотрите в сторону setjmp()/longjmp() --- этот механизм именно для этого и предназначен. Не так красиво, как в C++, но, при должной внимательности, работает достаточно надёжно.
лучше использовать setjmp и longjmp
Возможно, я ошибаюсь и ничего не понял, но такие вещи могут странно работать на разных моделях вызова (разная обработка стэка): у вас вызывается void throw(err_t e) который возвращает void, а вместо этого вы возвращаете data_t из функции try_catch (хоть NULL, хоть возврат от catch). В принципе, это можно обойти, если добавить некий контекст, там сохранить колбэк на catch и в throw сразу вызывать этот обработчик.
Ещё одна проблема/пожелалка: вы выделяете память. Даже так: когда всё хорошо, то вы не выделяете память; когда есть проблемы, то вы выделяете и освобождаете память. Это есть не очень хорошо для маленьких процессоров (где с памятью не всё хорошо) и для режима ядра, где работа с памятью - это ЛОК. В принципе, это можно обойти, если заранее закладывать поля в некую структуру "контекст прерывания".
Ещё есть идея try/catch загнать в препроцессор и сделать типа обвязки на кодом: пишем try, пишем код, пишем catch.
В общем, можно ещё добавить ненормальности, можно.
Мне нравятся такие эксперименты, продолжайте )
А возможно ли тут как-то сделать пробрасывание? Ну, если catch не определён, то вызвать throw вышестоящего метода, или типа того...
Можно завести стек, в функции try_catch добавлять в него текущий throw, а последний throw сделать глобальной функцией. Тогда выбросить ошибку можно будет почти отовсюду и она будет пробрасываться вверх по стеку, пока не встретит функцию catch. Более того, можно сделать коды (типы) ошибок и catch-ем ловить только те ошибки, которые он может обработать
Как верно подметил коллега выше, в стандартной библиотеке есть setjmp и longjmp. Пример использования этих вызовов для обработки исключений есть в известной утилите uci. Без никаких gcc 12 extensions
Оказывается, в C можно определять функции внутри функций
Можно, вот только в Си (стандартном) они имеют область видимости файла, а не блока, где определены. (В отличие от Pascal и прочих Виртовских языков, например.)
Более того, во вложенных функциях можно менять переменные из внешней функции и переходить по меткам из неё, но для этого необходимо, чтобы переменные были объявлены до вложенной функции, а метки явно указаны через __label__
А вот это уже совсем не Си (стандартный), а GCC диалект. Так что стоит заменить в заголовке «в C» на «в GCC Си».
ptr = (float *) try_catch
Указатель на void в Си преобразуем к указателю на любой другой тип: не нужно лишних явных преобразований.
Вообще нужно стараться избегать явных преобразований там, где без них можно обойтись: это корень зла — метод приказать компилятору заткнуться, повиноваться и делать, как программист сказал. Это бомба замедленного действия.
Паскалем повеяло... Доступ к переменным внешней функции (учитывая возможность рекурсии) – вообще говоря, нетривиальная задача (если по простому – вложенной функции нужен указатель на stack frame родительской), неудивительно, что это только в расширениях появилось.
Оказывается, в C можно определять функции внутри функций
Ну, оказывается, не в Си, а в одном из расширений для Си в составе gcc. В чистом стандартном Си ничего подобного нет. Если уж расширить выборку до всех на свете расширений языка Си, то в Microsoft-овском компиляторе Си есть расширение в виде ключевых слов _try/_except, которые дают готовый механизм обработки исключений, основанный на SEH.
Если же брать чистый стандартный Си, то механизм исключений может быть заполучен как результат использования стандартных функций setjmp/longjmp. Причем, если их обернуть в соответствующие макросы, внешне для программиста это будет выглядеть как типичное try/except.
Именно так, к примеру, сделано в исходниках VB/VBA, а значит этот механизм является частью VBA в составе Офисов, частью VB IDE и VB-рантайм-библиотеки.
Четверной лутц за один только заголовок статьи.
По моему мнению, исключения существуют только потому что существуют. Они приводят к дичайшему усложнению грамматики, ABI, проблемам при стандартизации и багам компиляторов и интерпретаторов. Если есть какой-то нерассмотренный случай, программа должна максимально быстро и информативно сообщить об этом разработчику, то есть с грохотом упасть, чтобы он этот случай рассмотрел и уже сам решил, как программа должна на него реагировать.
Одной системой типов можно заставить программиста явно рассматривать все актуальные локальные случаи ещё до компиляции. Потом за счёт анализа потока данных и тестов можно указать на ещё более редкие и пограничные случаи, хотя вся их редкость заключается в комбинации множества совершенно обычных случаев.
Безусловно должен быть способ как-то выкинуть исключение с пользовательским описанием, так же должен быть способ как-то его поймать, чтобы можно было написать свой дампер. Но использовать это как if-else или выход из глубокой рекурсии -- ремонт часов кувалдой.
Первый пример всё равно сводится к if-else. Если вам нужны именно локальные исключения, чем они отличаются от if-else? Эти вопросы всегда упираются в грамматику и возможности языка. Удобные optional или variadic на С будут намного полезнее.
Тут наверно проблема в том, что необходимость в "исключениях" появились позже чем появились функции.
Если бы эти понятия придумали одновременно, то думаю, что все было бы гораздо прозаичнее. Не нужно было бы разделять понятия "исключение" и "возврат", а была бы одна универсальная сущность, которая бы прерывала выполнение кода и разматывала стек до нужного места с возвращением результата.
Я думаю, наоборот, исключения и прыжки появились намного раньше функций. Каждый считает своим долгом реализовать в своей ОС/либе/языке механизм исключений и написать статью в их пользу, именно поэтому они до сих пор как-то котируются. Я от своего мнения не отказываюсь: исключения -- это либо if-else, либо неправильная архитектура кода
"Намного раньше" наверно речь идет об обработках ошибок без возможности продолжения выполнения программы (какой нибудь exception на майнфрейме или обработчик сигнала), но если говорить о возможности продолжения выполнения приложения после возникновения нештатной ситуации, да еще с возможностью корректного выхода из вложенных вызовов функций, то функции появились все же раньше.
А "долг" возникает из-за необходимости в реализации подобного общепринятого подхода. Если же "долг" отбросить и попробовать реализовать единый механизм возврата, то это вполне можно сделать (я не про синтаксис, а только про возможность возврата из вложенных функций) https://newlang.net/ops.html#прерывания-возврат-и-обработка-ошибок
Я исключения в одной программе применяю при чтении пакета данных из последовательного порта. Прочитал заголовок кадра и попытался сконструировать объект - кадр определённого типа. Если сообщение под него не подходит, конструктор кидает исключение, можно пробовать что-то ещё.
Довольно удобно, вся логика проверки соответствия полученных данных определенному формату инкапсулирована в классе, точнее, в конструкторе.
Можно оставить заголовок пустым, добавить метод valid или operator bool, а потом в if-else проверить. Скорее всего у пакета состояние memset(0) является невалидным, то есть такой метод не добавляет оверхеда. Да, тогда появляется возможность вызвать какие-либо методы у невалидного объекта, а исключение явно запрещает таким объектам конструироваться. То есть функция исключения в данном случае не в проверке, сконструировался объект или нет, а в запрещении использовать несконструированный. Если бы язык был чуть более умный в плане типизации, можно было бы запретить использовать объект, не проверив его на валидность без оверхеда. То есть исключения в данном случе тоже абсолютно не нужны. В вашем же коде можно достигнуть того же самого с помощью документации. Написать однострочный комментарий к функции проще, чем в каждом месте её вызова городить локальные функции и касты указателей.
Пишем try-catch в C не привлекая внимания санитаров