Как исключения, выделенные в стеке, перехватываются за пределами их области видимости?

StackOverflow https://stackoverflow.com/questions/2404288

Вопрос

В следующем коде основанная на стеке переменная 'ex' генерируется и перехватывается в функции, выходящей за пределы области видимости, в которой была объявлена ex.Мне это кажется немного странным, поскольку (AFAIK) переменные на основе стека не могут использоваться вне области видимости, в которой они были объявлены (стек разматывается).

void f() {
    SomeKindOfException ex(...);
    throw ex;
}

void g() {
    try {
        f();
    } catch (SomeKindOfException& ex) {
        //Handling code...
    }
}

Я добавил оператор print в деструктор SomeKindOfException, и он показывает, что ex уничтожается, как только он выходит из области видимости в f (), но затем он перехватывается в g () и снова уничтожается, как только он выходит из области видимости и там.

Какая-нибудь помощь?

Это было полезно?

Решение

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

Другие советы

Объект копируется в объект исключения это выдерживает разматывание стека.Откуда берется память для этого объекта, не указано.Для большого объекта это, вероятно, будет malloc'ed, а для объектов меньшего размера реализация могла бы иметь предварительно выделенный буфер (я мог бы представить, что это можно было бы использовать для bad_alloc исключение).

Ссылка ex затем привязан к этому объект исключения, который является временным (у него нет названия).

Стандарт C ++ 15.1/4:

Память для временной копии генерируемого исключения выделяется неуказанным способом, за исключением случаев, указанных в пункте 3.7.3.1.Временное сохраняется до тех пор, пока для этого исключения выполняется обработчик .В частности, если обработчик завершает работу, выполняя бросок;оператор, который передает управление другому обработчику для того же исключения, поэтому временное сохраняется.Когда последний обработчик, выполняемый для исключения , завершается любым способом, отличным от throw;временный объект уничтожается, и реализация может освободить память для временного объекта;любое такое освобождение выполняется неуказанным способом.Уничтожение происходит сразу после уничтожения объекта, объявленного в объявлении исключения в обработчике.

Мне больше нечего сказать.

Когда вы выбрасываете ex, он копируется в специальную ячейку памяти, используемую для выброшенных объектов исключения.Такое копирование выполняется обычным конструктором копирования.

Вы можете легко убедиться в этом на этом примере:

#include <iostream>

void ThrowIt();

class TestException
{
  public:
    TestException()
    {
        std::cerr<<this<<" - inside default constructor"<<std::endl;
    }

    TestException(const TestException & Right)
    {
        (void)Right;
        std::cerr<<this<<" - inside copy constructor"<<std::endl;
    }

    ~TestException()
    {
        std::cerr<<this<<" - inside destructor"<<std::endl;    
    }
};

int main()
{
    try
    {
        ThrowIt();
    }
    catch(TestException & ex)
    {
        std::cout<<"Caught exception ("<<&ex<<")"<<std::endl;
    }
    return 0;
}

void ThrowIt()
{
    TestException ex;
    throw ex;
}

Выборочный вывод:

matteo@teolapubuntu:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x
matteo@teolapubuntu:~/cpp/test$ ./ExceptionStack.x 
0xbf8e202f - inside default constructor
0x9ec0068 - inside copy constructor
0xbf8e202f - inside destructor
Caught exception (0x9ec0068)
0x9ec0068 - inside destructor

Кстати, здесь вы можете видеть, что ячейка памяти, используемая для выброшенного объекта (0x09ec0068), определенно находится далеко от ячейки исходного объекта (0xbf8e202f):стек, как обычно, имеет высокие адреса, в то время как памяти, используемой для выброшенного объекта, в виртуальном адресном пространстве очень мало.Тем не менее, это деталь реализации, поскольку, как указывались другие ответы, в стандарте ничего не говорится о том, где должна находиться память для брошенного объекта и как она должна быть выделена.

В дополнение к тому, что стандарт говорит в 15.1 / 4 ("Обработка исключений / создание исключения") - что память для временной копии генерируемого исключения выделяется неуказанным способом - есть еще пара мелочей о том, как выделяется объект exception:

  • 3.7.3.1/4 ("Функции распределения") стандарта указывает, что объект исключения не может быть выделен с помощью new выражение или вызов "глобальной функции распределения" (т.е. operator new() замена).Обратите внимание , что malloc() не является "глобальной функцией распределения", как определено стандартом, поэтому malloc() это определенно вариант для выделения объекта exception.

  • "Когда генерируется исключение, объект exception создается и обычно помещается в какой-то стек данных исключений" (Стэнли Липпман, "Inside the C ++ Object Model" - 7.2 Обработка исключений)

  • Из книги Страуструпа "Язык программирования C ++, 3-е издание":"Реализация C ++ необходима для того, чтобы иметь достаточно свободной памяти, чтобы иметь возможность выбрасывать bad_alloc в случае истощения памяти.Однако возможно, что создание какого-либо другого исключения приведет к исчерпанию памяти ". (14.4.5 Исчерпание ресурсов);и: "Реализация может применять широкий спектр стратегий для хранения и передачи исключений.Однако гарантируется, что имеется достаточно памяти, чтобы позволить new чтобы вызвать стандартное исключение из-за нехватки памяти, bad_alloc" (14.3 Перехват исключений).

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

Поскольку в спецификации явно указано, что временный объект создается вместо throw операнд.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top