Exception C# | Исключения C# и блоки try, catch, finally

|

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

Возникающие ошибки можно условно разделить на несколько основных типов.

Исключения в C#

Вот мы и подошли ближе к коду. Для обработки исключений используются четыре ключевых слова: try, catch. throw, finally. Также существуют классы исключительных ситуаций. Для всех исключений базовым классом является Exception. Класс содержит много полезного, но мы рассмотрим самые основы.

Исключение при работе приложения

Блоки try catch

Итак, мы можем предполагать, где именно возникнет ошибка. К примеру, в программе есть фрагмент, где ожидается ввод от пользователя. Так, кусок кода, обрабатывающий пользовательский ввод, может быть обернут в блок try. Следует заметить, что хорошим тоном является использование try catch только там, где это действительно необходимо. Пессимизм, конечно, может сэкономить нервы. Однако отладка подобного кода становится в некотором роде затруднительной.

В приведенном примере пользователю предлагается ввести два числа. Если был введен другой символ — пользователь получит сообщение об ошибке. Кроме того, в фрагменте кода был приведен только один блок catch. Однако при возможности возникновения нескольких ошибок могут быть указано несколько блоков catch.

Ошибки в приложениях с визуальным интерфейсом

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

Как вы видите, пользователь может продолжить исполнение программы после ошибки. По нажатию кнопки «Сведения» на экран будет выведена более подробная информация об ошибке. Несмотря на встроенный обработчик исключений следует обрабатывать ошибки самостоятельно. Как вариант, можно создать блок try, в котором отловить возникающую ошибку. А в блоке catch вывести сообщение об ошибке и соответствующим образом её обработать.

Генерирование исключительных ситуаций

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

Таким образом, мы прервем выполнение программы, если возникнет ошибка.

Блок finally

Ранее были мы рассмотрели только три ключевых слова: try, catch и throw. Исправим эту несправедливость и обратимся к finally. Удобство в том, что код в нем выполнится в любом случае, независимо от наличия или отсутствия ошибки в коде. К примеру, мы работаем с файлами. В таком случае, нам необходимо выполнить три действия: открыть файл, обработать данные, закрыть файл. Именно операция закрытия файла, которая должна выполняться в любом случае, выносится в блок finally.

Собственный класс исключения

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

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

Полагаю, это необходимый минимум, достаточный, чтобы познакомиться с исключениями в C#. Удачных экспериментов, коллеги.

Кроме того, рекомендую прочитать статью Map C# | Структура данных словарь C# А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.

С++ exception handling под капотом или как же работают исключения в C++

На хабре есть несколько статей, подробных и не очень (при этом все равно хороших), посвященных тому, как работают exceptions в C++. Однако нет ни одной по-настоящему глубокой, поэтому я решил восполнить этот пробел, благо есть подходящий материал. Кому интересно как работают исключения в C++ на примере gcc — запаситесь pocket-ом или evernote, свободным временем и добро пожаловать под кат.

2 часть
3 часть

P. S. Пару слов о переводе:

C++ исключения под капотом

Для начала нужно спросить себя: как это все работает? Это первая статья из длинной серии, которую я пишу о том, как реализованы исключения под капотом в C++ (под платформу gcc под x86, но должно быть применимо для других платформ так же). В этих статьях процес выброса и отлова ошибок будет объяснен во всех подробностях, но для нетерпеливых: короткий бриф всех статей о пробросе исключений в gcc/x86:

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

Если вы слишком любопытны, можете начинать тут. Это — полная спецификация того, что мы будем реализовывать в следующих частях. Я же попытаюсь сделать эту статью поучительной и более простой, чтобы в следующий раз вам было проще начинать с вашим собственным ABI (application binary interface, Двоичный интерфейс приложений — прим. переводчика).

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

Прим. переводчика: это актуально и для перевода.

C++ exceptions под капотом: маленький ABI

Если мы попытаемся понять, почему исключения такие сложные и как они работают, мы можем либо утонуть в тоннах мануалов и документаций, либо попытаться отловить исключения самостоятельно. В действительности, я был удивлен отстутствием качественной информации по теме (прим. переводчика — я, к слову, тоже): все, что можно найти либо чересчур детально, либо слишком уж простое. Конечно же, есть спецификации (наиболее документировано: ABI for C++, но так же CFI, DWARF и libstdc), но обособленного чтение документации недостаточно, если вы действительно хотите понять, что происходит внутри.

Давайте начнем с очевидного: с переизобретения колеса! Мы знаем, что в чистом C нет исключений, так что попытаемся слинковать C++ программу линкером чистого C и посмотрим, что произойдет! Я начал с чего-то простого типа этого:

И очень простой main:

Что случится, если мы попытаемся скомпилировать и слинковать этот франкинкод?

Заметка: вы можете загрузить весь исходный код для этого проекта с моего гит-репозитория.

Пока что все хорошо. Оба, g++ и gcc, счастливы в своем маленьком мире. Хаос начнется сразу, как только мы попробуем их слинковать вместе:

C++ exceptions под капотом: угождаем линкеру, подпихнув ему ABI

Тем не менее, мы хотим понять как именно работают исключения, так что попробуем реализовать свой собственный mini-ABI, обеспечивающий механизм пробрасывания ошибок. Чтобы сделать это, нам понадобится лишь RTFM, однако полный интерфейс может быть найден тут, для LLVM. Вспомним-ка, каких конкретно функций недостает:

__cxa_allocate_exception

Имя самодостаточно, я полагаю. __cxa_allocate_exception принимает size_t и выделяет достаточное количество памяти для хранения исключения во время его пробрасывания. Это сложнее, чем кажется: когда ошибка обрабатывается, происходит некая магия со стеком, аллоцирование (прим. переводчика — да простите за это слово, но иногда я буду его использовать) в стеке — плохая идея. Выделение памяти в куче (heap), в общем, тоже плохая идея, потому что где будем выделять память при исключении, сигнализирующем о том, что память закончилась? Статичное (static) размещение в памяти так же плохая идея, покуда нам нужно сделать это потокобезопасным (иначе два конкурирующих потока, выбросившие исключения, приведут к катастрофе). Учитывая эти проблемы, наиболее выгодным выглядит выделение памяти в локальном хранилище потока (куче), однако при необходимости обращаться к аварийному хранилищу (предположительно, статичному), если память закончилась (out of memory). Мы, конечно же, не будем волноваться по поводу страшных деталей, так что можем просто использовать статичный буфер, если понадобится.

__cxa_throw

vtable для __cxxabiv1::__class_type_info

Странно… __class_type_info явно какая-то RTTI (run-time type information, run-time type identification, Динамическая идентификация типа данных), но какая именно? Пока нам не просто ответить на это, да и это не адски важно для нашего мини-ABI; оставим это части «приложение», которую мы приведем после завершения анализа процесса пробрасывания исключения, сейчас же давайте просто скажем, что это — точка входа определения ABI в рантайме, отвечающая на вопрос: «эти два типа одинаковы или нет». Это функция, которая вызывается, чтобы определить: может ли данный catch-блок обрабатывать эту ошибку или нет. Сейчас мы сфокусируемся на основном: нам необходимо дать её как адрес для линкера (т. е. определить её не достаточно, нужно еще её инициировать) и она должна иметь vtable (да да, она должна иметь виртуальный метод).

Много работы происходит в этих функциях, но давайте попробуем реализовать простейший метатель исключений: тот, который будет делать выход из программы (call exit), когда исключение выброшено. Наше приложение почти завершено, но пропущены некоторые ABI-функции, так что давайте создадим mycppabi. cpp. Читая нашу ABI-спецификацию, мы можем описать наши сигнатуры для __cxa_allocate_exception и __cxa_throw:

Напомню: вы можете найти исходники в моем github репозитории.

Даже больше магии: когда throw транслируется в эти два вызова, компилятор даже не знает, как исключение будет обрабатываться. Как только libstdc++ определяет __cxa_throw и её друзей, libstdc++ динамически линкуется в рантайме, метод обработки исключений может быть выбран при первом запуске приложения.

Мы уже видим прогресс, но нам еще стоит пройти огромный путь познания. Сейчас наш ABI может только выбрасывать исключения. Можем ли мы расширить его, чтобы он отлавливал ошибки? Что ж, посмотрим, как это сделать, в следующей главе!

C++ exceptions под капотом: отлов того, что бросаем

В этой статье мы немного приоткрыли завесу тайны о пробросе исключений, наблюдая за ошибками компилятора и линкера, но мы до сих пор далеки до понимания чего-либо об отлове ошибок. Резюмируем что мы уже выяснили:

Как и ранее, мы имеем функцию seppuku, соединяющую C и C++ миры, только в этот раз мы добавили несколько вызовов функций, чтобы сделать наш стэк более интересным, также мы добавили ветви try/catch блоков, так что теперь мы можем анализировать как libstdc++ обрабатывает их.

И вновь получаем ошибки линковщика об отсутствующих ABI-функциях:

Мы опять видим кучу всего интересного. Вызов __cxa_begin_catch и __cxa_end_catch мы ожидали, хоть пока и не знаем, что они такое, но можем предположить, что они эквивалентны throw/__cxa_allocate/throw. __gxx_personality_v0 — что-то новое, и оно и будет основной темой следующих частей.

Что делает персональная функция? (при. переводчика — не придумал лучшего названия, подскажите в комментариях, если есть идеи). Мы уже что-то говорили о ней во введении, однако в следующий раз мы посмотрим на нее гораздо детальнее, как и на наших двух новых друзей: __cxa_begin_catch и __cxa_end_catch.

C++ exceptions под капотом: магия вокруг __cxa_begin_catch и __cxa_end_catch

После изучения того, как исключения выбрасываются, мы оказались на пути изучения, как они отлавливаются. В предыдущей главе мы добавили в наш пример приложения try-catch-блок, чтобы увидеть что делает компилятор, а так же получили ошибки линкера прямо как в прошлый раз, когда мы смотрели, что произойдет если добавить throw-блок. Вот что пишет линкер:

Напомню, что код вы можете получить на моем гит-репозитории.

В теории (в нашей теории, разумеется), catch-блок транслируется в пару __cxa_begin_catch/end_catch из libstdc++, но и во что-то новое, называемое персональной функцией, о который мы пока еще ничего не знаем.

Все идет замечательно: мы получили такое же определение для raise(), лишь выброс исключения:

Определение для try_but_dont_catch() обрезано компилятором. Это что-то новое: ссылка на __gxx_personality_v0 и что-то другое, называемое LSDA. Это выглядит незначительным определением, однако в действительности это очень важно:

Лишь обычный возврат функции… с некоторым мусором CFI в нем.

Это все для обработки ошибок, тем не менее, мы до сих пор не знаем, как работают __cxa_begin/end_catch; у нас есть идеи как эта пара формирует то, что называет landing pad — место в функции, где располагаются обраотчики исключений. Что мы пока не знаем — как landing pads ищутся. Unwind должен как-то пройти все вызовы в стеке, проверить: имеет ли какой-либо вызов (фрейм стека для точности) валидный блок с landing pad, который может обрабатывать это исключение, и продолжить выполнение в нем.

Это немаловажное достижение, и как это работает мы выясним в следующей главе.

C++ exceptions под капотом: gcc_except_table и персональная функция

Ранее мы выяснили, что throw транслируется в пару __cxa_allocate_exception/throw, а catch-блок транслируется в __cxa_begin/end_catch, а также во что-то, именуемое CFI (call frame information) для поиска landing pads — точки входа обработчиков ошибок.

Что мы не знаем до сих пор, это как _Unwind узнает, где этот landing pads. Когда исключение пробрасывается сквозь связку функций в стэке, все CFI позволяют программе разворачивания стэка узнать, что за функция сейчас исполняется, а так же это необходимо, чтобы узнать, какой из landing pads функции позволяет нам обрабатывать данное исключение (и, к слову, мы игнорируем функции с множественными try/catch блоками!).

Чтобы выяснить, где же этот landing pads находится, используется что-то, зовущее себя gcc_except_table. Таблица эта может быть найдена (с мусором CFI) после конца функции:

Эта секция .gcc_except_table — где хранится вся информация для обнаружения landing pads, мы поговорим об этом позже, когда будем анализировать персональную функцию. Пока что мы лишь скажем, что LSDA означает — зона с специфичными для языка данными, которые персональная функция проверяет на наличие landing pads для функции (она также используется для запуска деструкторов в процессе разворачивания стэка).

Подытожим: для каждой функции, где есть по крайней мере один catch-блок, компилятор транслирует его в пару вызовов cxa_begin_catch/cxa_end_catch и, затем, персональная функция, вызываемая __cxa_throw, читает gcc_except_table для каждого метода в стэке для поиска чего-то, называемого LSDA. Персональная функция затем проверяет, есть ли в LSDA блок, обрабатывающий данное исключение, а так же есть ли какой-то код очистки (который запускает деструкторы когда нужно).

Еще мы можем сделать интересный вывод: если мы используем nothrow (или пустой оператор throw), компилятор может опустить gcc_except_table для метода. Этот способ реализации исключений в gcc, не сильно влияющий на производительность, в действительности сильно влияет на размер кода. Что касается catch-блоков? Если исключение пробрасывается, когда объявлен спецификатор nothrow, LSDA не генерируется и персональная функция не знает, что ей делать. Когда персональная функция не знает, что ей делать, она вызывает обработчик ошибок по-умолчанию, что, в большинстве случаев, означает, что выброс ошибки из nothrow метода закончится std::terminate.

Теперь, когда у нас есть идеи, что делает персональная функция, сможем ли мы реализовать её? Что ж, посмотрим!

Ошибка EXCEPTION ACCESS VIOLATION — как исправить

Как исправить ошибку Exception Access Violation

При запуске игры или программы, а иногда и во время работы с ними вы можете столкнуться с ошибкой Exception Access Violation, в заголовке окна может быть информация об Unhandled Exception, Unexpected Error или Fatal Error, в тексте — коды наподобие 0xc0000005 или указание на DLL. Ошибка типична для Windows 10, предыдущих версий системы и, с большой вероятностью, останется и в Windows 11.

В этой инструкции о возможных способах исправить ошибку Exception Access Violation, которая, по сути, обычно сводится к невозможности какого-либо модуля программы или игры получить доступ к нужной области оперативной памяти.

Exception Access Violation как результат работы антивирусного ПО

Сообщение об ошибке Exception Access Violation

Среди наиболее часто встречающихся причин ошибки у русскоязычного пользователя, особенно при использовании нелицензионных игр или программ — антивирус: встроенный Windows Defender или какой-либо сторонний.

Возможные действия исправить Exception Access Violation для этого случая:

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

Дополнительные способы исправить ошибку

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

Также, если речь идёт о загруженной из Интернета (с неофициального сайта) программе, можно попробовать удалить её, а затем скачать из другого источника.

Видео

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

Источники:

https://shwanoff. ru/exception/

https://habr. com/ru/post/279111/

https://remontka. pro/exception-access-violation-error-fix/

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: