Как реализована среда выполнения обработки исключений C ++?


84

Меня заинтриговало, как работает механизм обработки исключений C ++. В частности, где хранится объект исключения и как он распространяется через несколько областей, пока не будет обнаружен? Он хранится в какой-то глобальной области?

Поскольку это может быть специфическим для компилятора, может ли кто-нибудь объяснить это в контексте пакета компиляторов g ++?


4
Прочтение этой статьи поможет вам
Ахмед Саид

Не знаю, но полагаю, что спецификация C ++ имеет четкое определение. (Хотя я могу ошибаться)
Пол Натан

2
Нет, в спецификации нет определения. Он диктует поведение, а не реализацию. Пол, вы можете указать, какая реализация вас интересует.
Роб Кеннеди,

1
Связанный вопрос: stackoverflow.com/questions/307610/…
CesarB

Ответы:


49

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

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

Что касается фактической передачи контроля, существует две стратегии. Один из них - записать достаточно информации в сам стек, чтобы раскрутить стек. По сути, это список деструкторов для запуска и обработчиков исключений, которые могут перехватить исключение. Когда происходит исключение, запустите стек, выполняя эти деструкторы, пока не найдете подходящий улов.

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


4
AFAIR g ++ использует второй подход, основанный на адресной таблице, предположительно по причинам совместимости с C. Компилятор Microsoft C ++ использует комбинированный подход, поскольку его исключения C ++ построены поверх SEH (структурированная обработка исключений). В каждой функции C ++ MSC ++ создает и регистрирует запись обработки исключений SEH, которая указывает на таблицу с диапазонами адресов для блоков try-catch и деструкторов в этой конкретной функции. throw упаковывает исключение C ++ как исключение SEH и вызывает RaiseException (), затем SEH возвращает управление подпрограмме обработчика, специфичной для C ++.
Антон Тихий

1
@Anton: да, он использует подход таблицы адресов. См. Мой ответ на другой вопрос на stackoverflow.com/questions/307610/… для подробностей.
CesarB

Спасибо за ответ. Вы можете видеть, как пуристы C могут бояться C ++ и его исключений. Идея о том, что простой try / catch может непреднамеренно создать несколько объектов стека во время выполнения или раздувать вашу программу дополнительными таблицами, является причиной, по которой встроенные системы часто избегают их.
Speedplane 05

@speedplane: Нет, это больше из-за непонимания. Обработка ошибок никогда не бывает бесплатной. C просто заставляет вас писать самому. И все мы знаем, сколько программ на языке C не имеют символа free()или символа fclose()в некоторых редко используемых путях кода.
MSalters 05

@MSalters Я не возражаю, это почти полное непонимание. Инженеры часто не понимают, как работают исключения и как исключения повлияют на их код, что, по праву, приводит к колебаниям при использовании исключений. Если бы реализация обработки исключений была более четко изложена (и не казалась магией), многие не решались бы их использовать.
Speedplane 05

20

Это определено в 15.1. Создание исключения из стандарта.

Бросок создает временный объект.
Как выделяется память для этого временного объекта, не определено.

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

Если исключение не генерируется повторно, временное состояние уничтожается в конце обработчика, в котором оно было обнаружено.

Примечание. Если вы перехватываете по ссылке, ссылка будет относиться к временному. Если вы перехватываете по значению, временный объект копируется в значение (и, следовательно, требует конструктора копирования).

Совет от С.Мейерса (ловить по постоянной ссылке).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}

3
Еще не определено, как программа раскручивает стек и как программа узнает, где находится «ближайший обработчик». Я почти уверен, что у Borland есть патент на один способ реализации этого.
Роб Кеннеди,

Пока объекты уничтожаются в порядке, обратном порядку создания, детали реализации не важны, если вы не инженер-компилятор.
Мартин Йорк

1
Проголосовали против: а) «Скотт Мейерс», а не «С. Майерс»; б) неверная цитата: «Эффективный C ++»: «Правило 13: Перехват исключений по ссылке ». Это позволит настраивать / добавлять информацию к объекту исключения.
Себастьян Мах

3
@phresnel: Не забывайте о пункте 21: «По возможности используйте const». Нет хорошего случая для настройки исключения. Вы должны: а) «исправить и отбросить», б) повторно выбросить или в) создать новое исключение.
Мартин Йорк

1
@phresnel: Да, у вас есть свои причины (не согласен с вашей логикой), у меня есть свои, и хотя я не буду утверждать, что говорил с ними об этом конкретном предмете или на самом деле знал их мнение (Мейерс, Александреску и Саттер), я считаю моя интерпретация остается в силе. Но если вы находитесь в районе Сиэтла, вы можете поговорить со всеми тремя, поскольку они регулярно посещают Северо-западную группу пользователей C ++ (Мейерс реже, чем другие).
Мартин Йорк

13

Вы можете посмотреть здесь подробное объяснение.

Также может быть полезно взглянуть на трюк, используемый в простом C для реализации некоторого базового вида обработки исключений. Это влечет за собой использование setjmp () и longjmp () следующим образом: первый сохраняет стек, чтобы отметить обработчик исключений (например, «catch»), а второй используется для «выброса» значения. «Выброшенное» значение выглядит так, как если бы оно было возвращено вызываемой функцией. «Блок попытки» заканчивается, когда снова вызывается setjmp () или когда функция возвращается.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.