如何在堆栈上分配的异常超出了它们的范围?

时间:2010-03-08 19:58:06

标签: c++ exception exception-handling callstack

在下面的代码中,基于堆栈的变量“ex”被抛出并被捕获到超出声明ex范围的函数中。这对我来说有点奇怪,因为(AFAIK)基于堆栈的变量不能在它们被声明的范围之外使用(堆栈被解开)。

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

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

我在SomeKindOfException的析构函数中添加了一个print语句,它显示ex在f()中超出范围后被破坏但是它被g()捕获并且一旦它超出范围就再次被破坏。

任何帮助?

6 个答案:

答案 0 :(得分:19)

异常对象被复制到一个特殊位置,以便在堆栈展开后继续存在。您看到两个析构的原因是因为当您退出f()时,原始异常将被销毁,当您退出g()时,副本将被销毁。

答案 1 :(得分:9)

将对象复制到异常对象中,该对象在堆栈展开后仍然存在。该对象的内存来自何处未指定。对于大对象,它可能是malloc'ed,对于较小的对象,实现可能有一个预分配的缓冲区(我可以想象这可以用于bad_alloc异常)。

然后将引用ex绑定到异常对象,这是一个临时对象(它没有名称)。

答案 2 :(得分:9)

C ++ Standard 15.1 / 4:

  

要抛出的异常的临时副本的内存以未指定的方式分配,   除非在3.7.3.1中注明。只要存在为此执行的处理程序,临时就会持续存在   例外。特别是,如果处理程序通过执行throw而退出;声明,将控制传递给另一个人   处理程序为同一个异常,所以临时保留。当最后一个处理程序被执行时   异常以抛出以外的方式退出;临时对象被销毁并执行   可以释放临时对象的内存;任何这种解除分配都是以未指明的方式完成的。   在销毁异常声明中声明的对象后立即发生破坏   在处理程序中。

没有什么可说的。

答案 3 :(得分:6)

当你抛出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):堆栈像往常一样具有高地址,而用于抛出对象的内存在虚拟地址空间中非常小。尽管如此,这是一个实现细节,因为正如其他答案所指出的那样,标准没有说明抛出对象的内存应该在何处以及应该如何分配。

答案 4 :(得分:3)

除了标准在15.1 / 4中所说的内容(“异常处理/抛出异常”) - 抛出的异常临时副本的内存以未指定的方式分配 - 其他一些琐事关于如何分配异常对象的是:

  • 标准的3.7.3.1/4(“分配函数”)表示异常对象不能通过new表达式或对“全局分配函数”的调用来分配(即。,operator new()替代品。请注意,malloc()不是标准定义的“全局分配函数”,因此malloc()绝对是分配异常对象的选项。

  • “当抛出异常时,异常对象被创建并且通常放在某种异常数据堆栈上”(Stanley Lippman,“在C ++对象模型中” - 7.2异常处理)

  • 来自Stroustrup的“The C ++ Programming Language,3rd Edition”:“要求C ++实现有足够的备用内存,以便在内存耗尽时抛出bad_alloc。但是,有可能抛出一些其他异常将导致内存耗尽。“(14.4.5资源耗尽);并且,“实现可能会应用各种各样的策略来存储和传输异常。但是,保证有足够的内存允许new抛出标准的内存不足异常,{{1 “(14.3捕捉异常)。

请注意,Stroustrup的引用是预标准的。我觉得有趣的是,该标准似乎并没有保证Stroustrup认为重要,足以提及两次。

答案 5 :(得分:1)

因为规范明确指出,创建了一个临时对象来代替throw操作数。