Як реалізовано середовище обробки винятків 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, який вказує на таблицю з діапазонами адрес для блоків і деструкторів спроби лову в цій конкретній функції. кидає пакети виняток C ++ як виняток SEH і викликає RaiseException (), тоді SEH повертає керування підпрограми обробника, специфічної для C ++.
Антон Тихий

1
@Anton: так, він використовує підхід до таблиці адрес. Детальніше див. Мою відповідь на інше запитання на stackoverflow.com/questions/307610/… .
CesarB

Дякую за відповідь. Ви можете бачити, як пуристи С могли боятися С ++ та його винятків. Ідея про те, що проста спроба / уловлювання може мимоволі створити ряд об'єктів стека під час виконання або роздути вашу програму додатковими таблицями, є причиною того, чому вбудовані системи часто уникають їх.
швидкісний літак

@speedplane: Ні, це більше через відсутність розуміння. Обробка помилок ніколи не є безкоштовною. C просто змушує писати це самостійно. І всі ми знаємо, скільки програм C не вистачає a free()чи an fclose()у деяких рідко використовуваних шляхах коду.
MSalters

@MSalters Я не погоджуюсь, це майже повністю відсутність розуміння. Інженери часто не розуміють, як працюють винятки і як вплинуть винятки на їх код, що потім, виправдано, призводить до вагань при використанні винятків. Якби реалізація обробки винятків була більш чітко передана (і не здавалася магією), багато хто б не вагався їх використовувати.
швидкісний літак

20

Це визначено в 15.1. Виключення стандарту.

Кидок створює тимчасовий об'єкт.
Не визначено, як виділяється пам’ять для цього тимчасового об’єкта.

Після створення тимчасового об'єкта управління передається найближчому обробнику в стеку викликів. розмотування стопки між кидком і точкою лову. У міру розмотування стека будь-які змінні стека знищуються в зворотному порядку створення.

Якщо виняток не буде кинуто повторно, тимчасовий знищується в кінці обробника, де він був спійманий.

Примітка: Якщо ви ловите за допомогою посилання, посилання буде стосуватися тимчасового, якщо ви ловите за значенням, тимчасовий об'єкт копіюється у значення (і, отже, потрібен конструктор копіювання).

Поради від S.Meyers (Ловіть за посиланням const).

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.