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

84

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

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

pjay
источник
4
Прочтение этой статьи поможет вам
Ахмед Саид
Не знаю, но полагаю, что спецификация C ++ имеет четкое определение. (Хотя я могу ошибаться)
Пол Натан
2
Нет, в спецификации нет определения. Он диктует поведение, а не реализацию. Пол, вы можете указать, какая реализация вас интересует.
Роб Кеннеди,
1
Связанный вопрос: stackoverflow.com/questions/307610/…
CesarB

Ответы:

49

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

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

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

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

MSalters
источник
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 () или когда функция возвращается.

Эдуард - Габриэль Мунтяну
источник