Рабочим названием платформы .NET было |
Язык CIL: обработка исключений. Синтаксис ILASM
Существует два основных способа перехвата ошибок, возникающих в процессе работы программы:
- Обработка кодов возврата.
Функция, выполнение которой может привести к ошибочной ситуации, возвращает некоторое значение, сообщающее, успешно или неуспешно функция выполнила свою задачу. Перехват ошибок заключается в том, что в коде, вызывающем такую функцию, стоят проверки ее возвращаемого значения.
Этот способ хорошо работает, если глубина стека вызовов функций в программе относительно невелика. В противном случае код программы из-за постоянных проверок становится громоздким и трудночитаемым.
- Обработка исключений.
Этот способ заключается в том, что в случае возникновения ошибки генерируется так называемая исключительная ситуация (исключение), которая описывается некоторым объектом. Генерация исключения приводит к передаче управления на фрагмент кода программы, называемый обработчиком исключения. Преимуществом такого подхода является то, что перехват ошибок локализован в отдельной части программы, а не распределен по всему коду, как в случае с обработкой кодов возврата.
Основная трудность для понимания деталей реализации обработки исключений в CLI заключается в том, что обработка исключений частично закодирована в телах методов (в виде специальных инструкций), а частично - в заголовках методов. Скорее всего, такая смешанная схема была выбрана разработчиками CLI для обеспечения компактности сборок. Поэтому мы в данном разделе сначала рассмотрим ту часть информации об обработке исключений, которая расположена в заголовках методов, затем перейдем к инструкциям CIL и, в конце концов, свяжем все воедино, приведя семантику обработки исключений виртуальной системой выполнения.
Предложения обработки исключений в заголовках методов
Для дальнейшего изложения нам понадобится ввести понятия области в коде метода и координат области.
Будем называть областью непрерывную последовательность инструкций в коде метода. При этом область будет определяться своими координатами, а именно парой чисел ( offset, length ), где offset - это смещение первой инструкции области относительно начала тела метода, а length - длина области. Как смещение, так и длину будем измерять в байтах.
Заголовок каждого метода содержит специальный массив, элементы которого называются предложениями обработки исключений (exception handling clause).
Каждое предложение обработки исключений представляет собой структуру, состоящую из нескольких полей. В этих полях записаны координаты двух или трех областей, а именно: в любом предложении присутствуют координаты защищенной области (protected block) и области обработчика (exception handler), а в некоторых предложениях дополнительно описана область фильтра (filter block).
Если говорить в терминах языка C#, то защищенная область - это try-блок, а область обработчика - это либо catch-блок, либо finally-блок. Аналог для области фильтра в языке C# отсутствует, но зато он есть в Visual Basic .NET и в Visual C++ with Managed Extensions. Область фильтра содержит код, принимающий решение о том, может ли данное исключение быть обработано обработчиком. Естественно, такое представление о назначении областей в предложении обработки исключений несколько примитивно и понадобится нам лишь на начальном этапе.
Давайте рассмотрим два возможных формата, в которых кодируется массив предложений обработки исключений. В каждом из двух форматов содержатся одни и те же поля, и различаются они только размерами полей. Первый формат называется коротким форматом (см. таблицу 3.43) и используется тогда, когда смещения областей не превышают 65535 байт, а длины областей не превышают 255 байт. Во втором, длинном формате (см. таблицу 3.44) допускаются любые смещения и длины. Строго говоря, не любые, а укладывающиеся в 32 бита. Но на практике этого более чем достаточно.
Итак, координаты защищенной области задаются парой ( TryOffset, TryLength ), а координаты области обработчика - парой ( HandlerOffset, HandlerLength ). Для области фильтра указывается только ее смещение, потому что подразумевается, что она непосредственно предшествует области обработчика (длину области фильтра можно вычислить: она равна HandlerOffset - FilterOffset ).
Обратите внимание, что смещения полей ClassToken и FilterOffset совпадают. Это означает, что фактически они представляют собой одно поле. Просто иногда оно интерпретируется как токен метаданных, а иногда - как смещение области фильтра.
Поле Flags, возможные значения которого перечислены в таблице 3.45, задает тип обработчика.
Всего возможны четыре типа обработчиков исключений, отличающихся друг от друга тем, по каким критериям принимается решение о передаче на них управления:
- Обработчик с фильтрацией по типу.
Получает управление, если тип исключения совместим по присваиванию с типом, указанным в поле ClassToken предложения обработки исключений.
- Обработчик с пользовательской фильтрацией.
Решение о том, получит или не получит управление обработчик, принимает код, содержащийся в области фильтра.
- Обработчик finally.
Вызывается при выходе из защищенной области, независимо от того, было или не было сгенерировано исключение.
- Обработчик fault.
Вызывается, если внутри защищенной области было сгенерировано любое исключение.
Первые два типа обработчиков мы будем относить к категории обработчиков с фильтрацией, а последние два - к категории обработчиков без фильтрации.