24

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

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

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

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

有什么帮助吗?

4

6 回答 6

21

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

于 2010-03-08T20:04:41.043 回答
10

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

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

于 2010-03-08T20:05:14.630 回答
10

C++ 标准 15.1/4:

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

没有什么可说的了。

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

于 2010-03-08T20:15:43.893 回答
4

除了标准在 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抛出标准的内存不足异常,bad_alloc”(14.3 捕获异常)。

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

于 2010-03-09T09:00:02.543 回答
1

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

于 2010-03-08T20:10:04.277 回答