7

我想弄清楚我抛出的对象在内存中的存储位置。所以我为它写了一个小程序:

#include <iostream>

#define print_dist() int a;\
    do { std::cout << __FUNCTION__ << "() a[" << (long)&a - (long)ptrMainStackBase << "]" << std::endl; } while (0)

#define print_distx(x) \
    do { std::cout << __FUNCTION__ << "() " #x "[" << (long)&x - (long)ptrMainStackBase << "]" << std::endl; } while (0)

#define print_distxy(x, y) \
    do { std::cout << __FUNCTION__ << "() " #x "(ex)[" << (long)&x - (long)y << "]" << std::endl; } while (0)

class CTest
{
    public:
        CTest()
        { std::cout << "CTest::CTest" << std::endl; }

//    private:
        CTest(const CTest&)
        { std::cout << "copy" << std::endl; }
};
const CTest *ptrException;
int *ptrMainStackBase;

void Test2()
{
    print_dist();
    CTest test;
    print_distx(test);
    std::cout << "&test=" << &test << std::endl;
    throw test;
}

void Test1()
{
    print_dist();
    try
    {
        Test2();
    }
    catch (const CTest& test)
    {
        ptrException = &test;
        print_dist();
        print_distx(test);
        print_distxy(test, ptrException);
        std::cout << "&test=" << &test << std::endl;
        throw test;
    }
}

int main()
{
    int b;
    ptrMainStackBase = &b;
    print_dist();
    try
    {
        print_dist();
        Test1();
    }
    catch (const CTest& test)
    {
        print_dist();
        print_distx(test);
        print_distxy(test, ptrException);
        std::cout << "&test=" << &test << std::endl;
    }

    return 0;
}

它打印:

main() a[-4]
main() a[-8]
Test1() a[-64]
Test2() a[-104]
CTest::CTest
Test2() test[-108]
&test=0x7fffd3b21628 <- test created here on stack
copy
Test1() a[-68]
Test1() test[-140736732956164]
Test1() test(ex)[0]
&test=0xb89090 <- and copied here
copy
main() a[-12]
main() test[-140736732956020]
main() test(ex)[144]
&test=0xb89120 <- and here

看起来,当我抛出一个对象时,它首先被复制到另一个远离正常堆栈的堆栈。这是真的?为什么两个“异常堆栈帧”之间有 144 字节的距离?

4

2 回答 2

6

当您抛出一个对象时,它确实首先被复制到某个临时位置。否则,堆栈展开将使其超出范围。然后通过引用捕获它会导致未定义的行为,如下所示:

void foo() {
   A a;
   throw a;
}

void bar() {
   try {
      foo();
   } catch (A& a) {
      // use a
   }
}

除非a已被复制到某个临时位置,否则您将引用一个不再存在于catch. 为此,A必须有一个公共的复制构造函数(除非您使用的是 VS,在这种情况下它也将使用一个私有的......)。此外,这是通过引用捕获的一个很好的理由 - 否则,您将有两个复制结构而不是一个。

于 2012-07-01T12:25:32.397 回答
2

看起来,当我抛出一个对象时,它首先被复制到另一个远离正常堆栈的堆栈。这是真的?

捕获异常时抛出的test不存在。那个原件test已经被破坏了。所以它必须是一个副本,并且新对象与参数和局部变量分开管理。换句话说,不在堆栈上。

它住在哪里?这取决于实施。很可能它是实现为您管理的动态内存(例如,堆)。

为什么两个“异常堆栈帧”之间有 144 字节的距离?

该标准没有说明在catch块中重新抛出异常时实现如何处理异常。由于throw局部变量必须进行复制,因此最简单的实现方法throw是始终进行复制。

于 2012-07-01T12:38:57.160 回答