Как исключения, размещенные в стеке, попадают за их пределы? - PullRequest
22 голосов
/ 08 марта 2010

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

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

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

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

Любая помощь?

Ответы [ 6 ]

19 голосов
/ 08 марта 2010

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

9 голосов
/ 08 марта 2010

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

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

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

9 голосов
/ 08 марта 2010

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

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

6 голосов
/ 08 марта 2010

Когда вы выбрасываете 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): стек, как обычно, имеет высокие адреса, а память, используемая для брошенного объекта, находится в виртуальном адресном пространстве. Тем не менее, это деталь реализации, поскольку, как указывалось в других ответах, стандарт ничего не говорит о том, где должна быть память для брошенного объекта и как она должна быть распределена.

3 голосов
/ 09 марта 2010

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

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

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

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

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

1 голос
/ 08 марта 2010

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

...