Как исключения, выделенные в стеке, выходят за рамки их возможностей?
В следующем коде переменная "ex" на основе стека выбрасывается и попадает в функцию, выходящую за пределы области, в которой был объявлен ex. Мне это кажется немного странным, поскольку переменные стека (AFAIK) не могут использоваться вне области, в которой они были объявлены (стек разворачивается).
void f() {
SomeKindOfException ex(...);
throw ex;
}
void g() {
try {
f();
} catch (SomeKindOfException& ex) {
//Handling code...
}
}
Я добавил инструкцию print для дескриптора SomeKindOfException, и он показывает, что ex разрушен, когда он выходит из области видимости в f(), но затем он попадает в g () и снова разрушается после того, как он выходит из области видимости,
Любая помощь?
Ответы
Ответ 1
Объект исключения копируется в специальное место, чтобы пережить разворот стека. Причина, по которой вы видите две разрушения, состоит в том, что, когда вы выходите из f(), исходное исключение уничтожается и когда вы выходите из g(), копия уничтожается.
Ответ 2
Объект копируется в объект исключения, который выживает при стирании стека. Если память для этого объекта не указана, не указывается. Для большого объекта он, вероятно, будет malloc
'ed, а для более мелких объектов реализация может иметь предварительно выделенный буфер (я могу представить, что это может быть использовано для исключения bad_alloc
).
Ссылка ex
затем привязана к этому объекту исключения, который является временным (он не имеет имени).
Ответ 3
С++ Standard 15.1/4:
Память для временной копии генерируемого исключения распределяется неопределенным способом, за исключением случаев, указанных в п. 3.7.3.1. Временное сохранение сохраняется до тех пор, пока выполняется обработчик для этого исключение. В частности, если обработчик завершает выполнение броска; утверждение, которое передает управление другому обработчик для того же исключения, поэтому временное остается. Когда последний обработчик выполняется для исключение выходит любым способом, кроме броска; временный объект уничтожается и реализация может освободить память для временного объекта; любое такое освобождение производится неуказанным способом. Уничтожение происходит сразу же после уничтожения объекта, объявленного в описании исключения в обработчике.
Больше нечего сказать.
Ответ 4
Когда вы выбрасываете 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;
}
Пример вывода:
[email protected]:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x
[email protected]:~/cpp/test$ ./ExceptionStack.x
0xbf8e202f - inside default constructor
0x9ec0068 - inside copy constructor
0xbf8e202f - inside destructor
Caught exception (0x9ec0068)
0x9ec0068 - inside destructor
Кстати, вы можете видеть, что расположение памяти, используемое для брошенного объекта (0x09ec0068), безусловно, далеко от исходного объекта (0xbf8e202f): стек, как обычно, имеет высокие адреса, а память, используемая для заброшенного объекта, довольно проста в виртуальном адресном пространстве. Тем не менее, это детализация реализации, поскольку, как указывали другие ответы, стандарт ничего не говорит о том, где должна быть память для брошенного объекта и как его следует распределять.
Ответ 5
В дополнение к тому, что стандарт говорит в 15.1/4 ( "Обработка исключений/Бросок исключения" ) - то, что память для временной копии генерируемого исключения распределяется неуказанным способом - несколько других бит мелочей о том, как выделяется объект исключения:
-
3.7.3.1/4 ( "Функции распределения" ) стандарта указывает, что объект исключения не может быть выделен выражением new
или вызовом глобальной функции распределения (т.е. a operator new()
). Обратите внимание, что malloc()
не является "глобальной функцией распределения", как определено стандартом, поэтому malloc()
определенно является опцией для выделения объекта исключения.
-
"Когда генерируется исключение, создается объект исключения и помещается, как правило, в какой-либо стек данных исключений" (Stanley Lippman, "Inside the С++ Object Model" - 7.2 Обработка исключений)
-
Из Stroustrup "Язык программирования С++, 3-е издание": "Для реализации С++ требуется достаточная запасная память, чтобы иметь возможность бросать bad_alloc
в случае исчерпания памяти. Однако возможно, что метание другое исключение приведет к исчерпанию памяти". (14.4.5 Исчерпание ресурсов); и: "Реализация может применять множество стратегий для хранения и передачи исключений. Однако гарантируется, что имеется достаточная память, чтобы позволить new
выбрасывать стандартное исключение из памяти, bad_alloc
" ( 14.3. Исключение исключений).
Обратите внимание, что цитаты из Stroustrup являются предварительными. Мне интересно, что стандарт, похоже, не гарантирует гарантии того, что Страуструп считает достаточно важным, чтобы упомянуть дважды.
Ответ 6
Поскольку спецификация явно заявляет, что вместо операнда throw
создается временный объект.