RAII и раскручивание стека

TIL, что мои представления о «переплетении» (из-за отсутствия лучшего слова) RAII и разматывании стека совершенно (если не полностью) неверны. Насколько я понимаю, использование RAII защищает от любых/всех утечек ресурсов - даже тех, которые потенциально вызваны необработанными исключениями.

Однако, написав эту тестовую программу и впоследствии наткнувшись на эта статья/документация заставила меня понять, что стек раскручивание приведет только к тому, что освобождение ресурсов с поддержкой RAII сработает только для автоматических в блоке try, в отличие от автоматических, скажем, во внешних/других областях.

Я прав в этом (новом) понимании? Или есть еще нюансы, которых я еще не улавливаю? Кто-нибудь из гуру хочет присоединиться? Указания на любые хорошие описания/анализы/объяснения (разматывания стека) были бы полезны/оценены…


person decimus phostle    schedule 05.04.2011    source источник
comment
Стандарт можно найти здесь: stackoverflow.com/questions/81656/   -  person Martin York    schedule 06.04.2011


Ответы (3)


Из стандарта С++ 03, §15.3/9:

Если в программе не найден подходящий обработчик, вызывается функция terminate(); независимо от того, был ли стек раскручен до того, как этот вызов функции terminate() определяется реализацией (15.5.1).

§15.5.1/1:

В следующих ситуациях от обработки исключений следует отказаться в пользу менее изощренных методов обработки ошибок: ... когда механизм обработки исключений не может найти обработчик для сгенерированного исключения (15.3) ...

§15.5.1/2:

В таких случаях

void terminate();

называется (18.6.3). В ситуации, когда соответствующий обработчик не найден, реализация определяет, будет ли раскручен стек перед вызовом метода terminate(). Во всех других ситуациях стек не должен раскручиваться до вызова функции terminate(). Реализации не разрешается преждевременно завершать раскручивание стека на основании определения того, что процесс раскручивания в конечном итоге приведет к вызову terminate().

person ildjarn    schedule 05.04.2011
comment
Этот документ есть в сети? Если да, не могли бы вы добавить ссылку на тот, который вы цитируете? Спасибо. (Мои навыки поиска не на высоте, и я, кажется, не могу его найти.) - person decimus phostle; 06.04.2011
comment
@decimus phostle : Это стандарт ISO, так что нет, не бесплатно, но вы можете купить его здесь: iso.org/iso/catalogue_detail.htm?csnumber=38110. Кроме того, вы можете бесплатно прочитать черновики C++0x, последний из которых находится здесь: open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf. Однако имейте в виду, что многие, многие изменения относятся к C++0x, поэтому многое из того, что там содержится, применимо только к передовым компиляторам. - person ildjarn; 06.04.2011
comment
@decimus: полное название текущего стандарта — ISO/IEC 14882:2003(E). Погугли это. Или, как говорит ildjarn, с нетерпением жду следующей версии. Его ратифицируют в ближайшие месяцы, но пока никто не реализует его в полной мере. - person Steve Jessop; 06.04.2011

Вы правы, что "раскручивание стопки" происходит на пути от throw some_exception к catch(some_exception). Если ваше исключение никогда не достигает улова, мы не знаем, что произойдет.

Это большая проблема? Как вы сами показали, вам просто нужно где-нибудь добавить catch(...), чтобы перехватывать все возможные исключения, и проблема исчезнет.

person Bo Persson    schedule 05.04.2011
comment
Что произойдет, если исключение не поймано, известно. Просто прочитайте ответ Ильджарн. - person Tobias; 06.04.2011
comment
Программа, конечно, завершится, но неизвестно, произойдет ли сначала раскрутка стека. Это легко исправить, добавив catch(...). - person Bo Persson; 06.04.2011
comment
Точнее, чем неизвестно: когда выброшенное исключение не имеет соответствующего catch, реализация определяет, раскручивается ли стек до вызова terminate. - person aschepler; 06.04.2011

Стандарт определяет три способа завершения выполнения программы на C++:

  • Возвращение из main. Объекты с автоматическим хранением (функция-локальная) уже уничтожены. Объекты со статическим хранилищем (глобальным, статическим по классу, статическим по функциям) будут уничтожены.
  • std::exit из <cstdlib>. Объекты с автоматическим хранением НЕ уничтожаются. Объекты со статическим хранилищем будут уничтожены.
  • std::abort из <cstdlib>. Объекты с автоматическим и статическим хранением НЕ уничтожаются.

Также актуален std::terminate из <exception>. Поведение terminate можно заменить с помощью std::set_terminate, но terminate всегда должно «завершать выполнение программы», вызывая abort или другую аналогичную альтернативу, зависящую от реализации. По умолчанию просто { std::abort(); }.

C++ будет вызывать std::terminate всякий раз, когда возникает исключение, а C++ не может разумно выполнить раскрутку стека. Например, исключение из деструктора, вызванного раскручиванием стека, или исключение из конструктора или деструктора статического объекта хранения. В этих случаях раскручивание стека (больше) не выполняется.

C++ также вызовет std::terminate, если соответствующий обработчик catch не будет найден. В этом единственном случае C++ может необязательно развернуться до main перед вызовом terminate. Таким образом, ваш пример может иметь разные результаты с другим компилятором.

Итак, если вы правильно используете RAII, оставшиеся шаги для «защиты от утечек» вашей программы:

  • Избегайте std::abort.
  • Либо избегайте std::exit, либо избегайте всех объектов со статической продолжительностью хранения.
  • Поместите обработчик catch (...) в main и убедитесь, что ни в нем, ни после него не происходит выделений или исключений.
  • Avoid the other programming errors that can cause std::terminate.
    • (On some implementations, functions compiled with a C compiler act like they have C++'s empty throw() specification, meaning that exceptions cannot be thrown "past" them even though they have no destructors to be called.)
person aschepler    schedule 06.04.2011