C++ 调用terminate时是否调用自动对象的析构函数?

C++ 调用terminate时是否调用自动对象的析构函数?,c++,stack,throw,brainbench,C++,Stack,Throw,Brainbench,当我们从析构函数中抛出时会发生什么?我知道它会导致调用terminate(),内存确实被释放,析构函数被调用,但是,这是在从foo调用throw之前还是之后?这里的问题可能是在堆栈展开时使用了throw,这就是问题所在。对象a是堆栈对象,因此没有要释放的动态内存。一旦控件超出了foo()的范围,堆栈框架和对象就不再存在了。为了说明这一点,下面是Microsoft C++中发生的情况: #include <iostream> class A { public: ~A() {

当我们从析构函数中抛出
时会发生什么?我知道它会导致调用
terminate()
,内存确实被释放,析构函数被调用,但是,这是在从
foo
调用
throw
之前还是之后?这里的问题可能是在堆栈展开时使用了
throw
,这就是问题所在。

对象
a
是堆栈对象,因此没有要释放的动态内存。一旦控件超出了
foo()
的范围,堆栈框架和对象就不再存在了。

为了说明这一点,下面是Microsoft C++中发生的情况:

#include <iostream>

class A {
public:
    ~A() {
        std::cout << "in ~A" << std::endl;
        throw "error";
    }
};

void foo() {
    A a;
    std::cout << "in foo" << std::endl;
    throw "error";
}

int main() {

    try {
        foo();
    }
    catch (void *) {
        std::cout << "exit: 1" << std::endl;
        return 1;
    }
    std::cout << "exit: 0" << std::endl;
    return 0;
}

如果我没有弄错的话,一旦调用了
terminate
,就不会发生(进一步的)堆栈展开

terminate
调用处理程序函数(可以使用
set\u terminate
设置该函数):

要执行的处理程序函数的类型 终止时由terminate()调用 异常处理。
必需 行为: 终止处理程序应终止程序的执行 不返回给呼叫方。
默认行为: 实现的默认terminate_处理程序调用abort()

至少我不知道有什么方法可以“在不返回调用方的情况下终止执行”,从而允许您展开堆栈

您可以修改该示例以了解您的期望:

#include <cstdio>

class A
{
    public:
        ~A() {
            puts("Entered A destructor");
            throw "error";
        }
};

void foo()
{
    A a, b;
    throw "error";
}

int main()
{
    try {
        foo();
    } catch (const char*) {
        return 1;
    }
}
#包括
甲级
{
公众:
~A(){
puts(“输入析构函数”);
抛出“错误”;
}
};
void foo()
{
A,b;
抛出“错误”;
}
int main()
{
试一试{
foo();
}捕获(常量字符*){
返回1;
}
}
现在有两个A实例,第二个实例的析构函数永远不会被调用,因为第一个A的析构函数一完成,执行就被终止,并让另一个异常逃逸

这是在foo调用throw之前还是之后

这就是正在发生的事情:

  • 调用
    foo()
  • 在堆栈上创建类型为
    a
    的对象
    a
  • 下一个语句抛出
  • 现在,调用
    a
    的dtor,这会引发另一个异常
  • 调用了
    std::terminate
    ——这只是放弃异常处理机制:
从C++0x草稿:

15.5.1 std::terminate()函数

1以下情况除外 必须放弃处理,以减少成本 微妙的错误处理技巧:

[……] -当毁灭 堆栈展开期间的对象 (15.2)使用异常退出,或

2在这种情况下,std::terminate()是 称为(18.7.3)。在这种情况下 如果找不到匹配的处理程序,则 是否定义了实现 之前没有展开堆栈 调用std::terminate()。总共 其他情况下,烟囱不得 在调用std::terminate()之前展开 打电话。一个实现是不可能的 允许完成堆栈展开 过早地基于一个决定 放松过程将 最终导致打电话给 std::terminate()

注:我的重点


下面是在g++中发生的事情:

#include <stdio.h>
class A {
public:
    ~A()
    {
        fprintf(stderr, "in ~A\n");
        throw "error";
    }
};

void foo()
{
    A a;
    fprintf(stderr, "in foo\n");
    throw "error";
}

int main()
{
    try {
        foo();
    }
    catch (const char*) {
        return 1;
    }
    return 0;
}


[~/ecc/ellcc/ecc] main% ./a.out
in foo
in ~A
terminate called after throwing an instance of 'char const*'
Abort
[~/ecc/ellcc/ecc] main% 
#包括
甲级{
公众:
~A()
{
fprintf(stderr,“in~A\n”);
抛出“错误”;
}
};
void foo()
{
A A;
fprintf(标准格式,“foo\n”);
抛出“错误”;
}
int main()
{
试一试{
foo();
}
捕获(常量字符*){
返回1;
}
返回0;
}
[~/ecc/ellcc/ecc]main%./a.out
因福
~A
在抛出“char const*”实例后调用terminate
中止
[~/ecc/ellcc/ecc]main%

正如您所看到的,抛出foo首先发生,然后抛出~A导致错误。

您得到的结果稍有错误,这就是您不理解它的原因。您知道,抛出析构函数并不会导致调用
teriminate()
函数,这是一种不好的做法,但对程序执行来说并不是致命的。致命的是,一些代码在仍然存在活动异常时抛出。C++不能决定进一步传播什么样的异常,新的或旧的异常,不能传播它们。它被认为对程序执行是致命的,这就是调用terminate的原因


因此,如果没有抛出
foo
,将不会调用terminate,但会从
~A
抛出异常。因此,很自然,必须首先调用throw-in-foo,然后在第二次throw过程中,所有内容都会中断。

实际上,我相信您可以说,堆栈上的内存一旦超出范围,即使不是动态的,也会被释放。然而,这不是这里的问题,我也在问在析构函数中使用
throw
。就像许多Brainbench问题一样,这属于“如果你做了一些你永远不应该做的事情会发生什么?”的范畴,因此不值得费心回答。@Neil,有时候这样的答案还是有用的。如果你知道如果你做了
X
,就会发生
S
,那么如果你在调试时看到
S
,你就会看到你(或其他人)在某处做过的
X
@Neil:是的,抛出一个
const char*
。这太令人震惊了;-)我认为问题是好的,我宁愿有一个程序员知道为什么不从析构函数中抛出,因为他知道后果,而不是一个被他信任的权威命令不这样做,从不这样做,但不知道或不关心他为什么遵循这条规则的程序员。请注意,在这两者之间做出选择是一个好的开始。@Steve我的观点是,给定代码,标题问题并不是真正可以回答的。内存被释放了吗?是的,我想是这样的-如果调用了terminate,应用程序退出,所有内存都被释放,那又怎样?@nbolton:我想你可能要找的术语是“堆栈展开”。也许更好的标题是:“你是谁?”
#include <stdio.h>
class A {
public:
    ~A()
    {
        fprintf(stderr, "in ~A\n");
        throw "error";
    }
};

void foo()
{
    A a;
    fprintf(stderr, "in foo\n");
    throw "error";
}

int main()
{
    try {
        foo();
    }
    catch (const char*) {
        return 1;
    }
    return 0;
}


[~/ecc/ellcc/ecc] main% ./a.out
in foo
in ~A
terminate called after throwing an instance of 'char const*'
Abort
[~/ecc/ellcc/ecc] main%