Как компилятор C++ реализует обработку исключений - C++ и исключения - продолжение

ОГЛАВЛЕНИЕ

 

C++ и исключения - продолжение

Рисунок 5 показывает схему структуры funcinfo. Имена могут отличаться от фактических имен, используемых компилятором VC++. На рисунке показаны только значимые поля. Структура таблицы раскрутки описана в следующем разделе.

Программирование, исходники, операционные системы - Как компилятор C++ реализует обработку исключений

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

Сперва рассмотрим, как выполняется обнаружение блока try. Во время компиляции компилятор назначает каждому блоку try идентификатор начала и идентификатор конца. Эти идентификаторы также доступны для обработчика исключений через структуру funcinfo. Смотрите рисунок 5. Компилятор создает структуру данных tryblock для каждого блока try внутри функции.

В предыдущем разделе говорилось о VC++, расширяющем структуру EXCEPTION_REGISTRATION, чтобы включить в нее поле идентификатора. Эта структура присутствует в кадре стека функции. Смотрите рисунок 4. Во время исключения обработчик исключений читает этот идентификатор из кадра и проверяет структуру tryblock, чтобы увидеть, равняется ли этот идентификатор или находится между его идентификатором начала и идентификатором конца. Если это имеет место, то исключение возникло внутри этого блока try. В противном случае он ищет в следующей структуре tryblock в tryblocktable.

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

Когда обработчик исключений нашел блок try, он может просмотреть таблицу catchblock, связанную с блоком try, чтобы увидеть, заинтересован ли какой-либо блок catch в захвате исключения. В случае вложенных блоков try, исключение, возникшее во внутреннем блоке try, также возникает во внешнем блоке try. Обработчик исключений сначала должен найти блоки catch для внутреннего блока try. Если ничего не найдено, то он ищет блоки catch внешнего блока try. Во время размещения структур в таблице tryblock VC++ помещает структуру внутреннего блока try перед наружным блоком try.

Как обработчик исключений будет определять (из структуры catchblock), заинтересован ли блок catch в захвате текущего исключения? Он делает это путем сравнения типа исключения с типом параметра блока catch. Рассмотрите:

void foo()
{
   try {
      throw E();
   }
   catch(H) {
      //.
   }
}

Блок catch захватывает исключение, если H и E имеют абсолютно одинаковый тип. Обработчик исключений должен сравнивать типы во время выполнения. Обычно такие языки, как C, не предоставляют никаких средств для определения типа объекта во время выполнения. C++ предоставляет механизм определения типа во время выполнения механизм (RTTI) и имеет стандартный метод сравнения типов во время выполнения. Он определяет класс type_info, определенный в стандартном заголовке <typeinfo>, представляющем тип во время выполнения. Второе поле структуры catchblock (смотрите рисунок 5) – указатель на структуру type_info, представляющую тип параметра блока catch во время выполнения. type_info имеет operator ==, сообщающий, принадлежат ли два типа к абсолютно такому же классу или нет. Обработчик исключений должен сравнить (вызвать operator ==) type_info параметра блока catch (доступен через структуру catchblock) с type_info исключения, чтобы определить, заинтересован ли блок catch в захвате текущего исключения.

Обработчик исключений знает о типе параметра блока catch из структуры funcinfo, но как он узнает о type_info исключения? Когда компилятор встречает такой оператор, как:

throw E();

Он создает структуру excpt_info strcuture для выброшенного исключения. Смотрите рисунок 6. Имена могут отличаться от фактических имен, используемых компилятором VC++, и были показаны только значимые поля. Как показано на рисунке, type_info исключения доступен через структуру excpt_info. К какой-то момент времени обработчик исключений должен уничтожить исключение (после вызова блока catch). Ему может потребоваться скопировать исключение (перед вызовом блока catch). Чтобы помочь обработчику исключений выполнить эти задачи, компилятор предоставляет обработчику исключения доступ к деструктору исключения, конструктору копирования исключения и его размеру через структуру excpt_info.

Программирование, исходники, операционные системы - Как компилятор C++ реализует обработку исключений

Если тип параметра блока catch - базовый класс, а исключение - его производный класс, обработчик исключений все-таки должен вызвать этот блок catch. Но сравнение двух typeinfo блока захвата и исключения в этом случае дало бы ложь, так как они не одинакового типа. Класс type_info тоже не предоставляет никакой функции-члена или оператора, сообщающего, является ли один класс базовым классом другого. Несмотря на это, обработчик исключений должен вызвать этот блок catch. Чтобы помочь ему сделать это, компилятор сгенерировал больше информации для обработчика. Если исключение – производный класс, то etypeinfo_table (доступен через структуру excpt_info) содержит указатель etype_info (расширенный type_info, мое имя) для всех классов в иерархии. Обработчик исключений сравнивает type_info параметра блока catch со всеми type_info, доступными через структуру excpt_info. Если найдено какое-либо совпадение, то будет вызван блок catch.

Последний вопрос перед завершением этого раздела: Как обработчик исключений узнает об исключении и о структуре excpt_info? Ниже дан ответ на данный вопрос.

VC++ переводит оператор выброса в что-то вроде:

//throw E(); //компилятор создает структуру excpt_info для E.
E e = E();  //создание исключения в стеке
_CxxThrowException(&e, E_EXCPT_INFO_ADDR);


_CxxThrowException передает управление операционной системе (через программное прерывание, смотрите функцию RaiseException), передавая ей оба своих параметра. Операционная система упаковывает эти два параметра в структуру _EXCEPTION_RECORD во время подготовки к вызову обратного вызова исключения. Он начинает с головы списка EXCEPTION_REGISTRATION, на которую указывает FS:[0], и вызывает обработчик исключений в этом узле. Указатель на этот EXCEPTION_REGISTRATION также является вторым параметром обработчика исключений. Вспомните, что в VC++ каждая функция создает свой собственный EXCEPTION_REGISTRATION в своем кадре стека и регистрирует его. Передача второго параметра обработчику исключений делает важную информацию доступной для него, как поле идентификатора EXCEPTION_REGISTRATION (важно для отыскания блока catch). Она также сообщает обработчику исключения о кадре стека функции (полезно для очистки кадра стека) и позиции узла EXCEPTION_REGISTRATION в списке исключений (полезно для раскрутки стека). Первый параметр – указатель на структуру _EXCEPTION_RECORD, через которую доступны указатель исключения и его структура excpt_info. Сигнатура обработчика исключений, определенного в EXCPT.H, такова:

EXCEPTION_DISPOSITION (*handler)(
    _EXCEPTION_RECORD *ExcRecord,
    void * EstablisherFrame,
    _CONTEXT *ContextRecord,
    void * DispatcherContext);

Можно игнорировать два последних параметра. Тип возвращаемого значения - перечисление (смотрите EXCPT.H). Как сказано выше, если обработчик исключений не может найти блок catch, он возвращает значение ExceptionContinueSearch обратно в систему. Для данного обсуждения другие значения не важны. Структура _EXCEPTION_RECORD определена в WINNT.H как:

struct _EXCEPTION_RECORD
{
    DWORD ExceptionCode;
    DWORD ExceptionFlags;
    _EXCEPTION_RECORD *ExcRecord;
    PVOID   ExceptionAddress;
    DWORD NumberParameters;
    DWORD ExceptionInformation[15];
} EXCEPTION_RECORD;

Количество и вид элементов в массиве ExceptionInformation зависит от поля ExceptionCode. Если ExceptionCode обозначает исключение C++ (код исключения 0xe06d7363) (который будет иметь место, если исключение происходит из-за выбрасывания), то массив ExceptionInformation содержит указатель на исключение и на структуру excpt_info. Для других видов исключений он почти всегда не содержит никаких элементов. Другими видами исключений могут быть деление на ноль, нарушение прав доступа и т.д. Их значения можно найти в WINNT.H.

Обработчик исключений смотрит на поле ExceptionFlags структуры _EXCEPTION_RECORD, чтобы определить, какое действие предпринять. Если значение - EH_UNWINDING (определено в Except.inc), то для обработчика исключений это указание на то, что стек раскручивается, и что он должен очистить свой кадр стека и вернуться. Очистка включает обнаружение всех локальных объектов, находящихся в кадре в момент исключения, и вызов их деструкторов. Это описано в следующем разделе. В противном случае, обработчик исключений должен искать блок catch block в функции и вызвать его в случае обнаружения.