在结构化异常情况下的堆栈展开

4

这个问题提供了更多关于此处描述的问题的澄清。我进行了更多的调查,发现以下代码段中没有发生堆栈展开:

class One
{
public:
    int x ;
};

class Wrapper
{
public:
    Wrapper(CString csText):mcsText(csText)
    {
        CString csTempText;
        csTempText.Format("Wrapper constructor :: %s\n", mcsText);
        OutputDebugString(csTempText);
    }

    ~Wrapper()
    {
        CString csTempText;
        csTempText.Format("Wrapper destructor :: %s\n", mcsText);
        OutputDebugString(csTempText);
    }
    CString mcsText;
};
class Test
{
    public:

    void notifyError()
    {
        try
        {
            int x = 10; 
        }
        catch(...)  {}
    }

    void OnRecvBuffer()
    {
        try
        {
            Wrapper a("AddRef");    
            One* p = NULL;
            p->x = 10;
        }
        catch(...)
        {
            notifyError();
        }   
    }   
};



int main() 
{
    Test* pTest = new Test;

    pTest->OnRecvBuffer();

    OutputDebugString("Test");
}

我使用VC6 SP5编译器编译了这段代码,输出为“Wrapper constructor :: AddRef !!!”(即在堆栈上构造的包装器对象的析构函数未被调用)。这是否是预期行为?还是VC编译器的一个错误?我能否使用一些编译器标志使堆栈展开在这种情况下发生?

4个回答

7
C++标准在出现未定义行为时并没有提供任何处理方法,即使微软有。这是一个特定于平台的问题,所以要小心。一些浮点异常被转换为Win32异常,您可以尝试使用_set_se_translator()捕获它们。问题是您可以捕获Win32异常,但是您的堆栈将无法正确展开。至少这不是您可以赌上自己生命的事情。这就是练习的无用之处所在。
更新:意图抛出异常来检查堆栈展开。问题是Wrapper类的析构函数为什么没有被调用。
如果是这种情况,请不要这样做。有比通过未定义行为抛出异常更好的方法。
例如:
void OnRecvBuffer()
{
    try
    {
        Wrapper a("AddRef");    
        throw 42; // this'll throw an exception without invoking UB
    }
    catch(...)
    {
        notifyError();
    }
}

您不能对空指针进行解引用操作。在这里,您正在调用未定义的行为:

One* p = NULL;
p->x = 10;

在那行之后,一切皆有可能,你可能会杀死我们所有人 ;)

p 是指向 One 对象的指针。它应该包含一个 One 对象的地址。你将其初始化为 0 -- 在地址 0 上没有任何对象。对于任何对象来说,0 都不是有效的地址(这是标准保证的)。


异常被有意地抛出以检查堆栈展开。问题是为什么包装类的析构函数没有被调用。 - Naveen
正如我所指出的,这是未定义行为--当您调用未定义行为时,发生了什么是不确定的。没有讨论的必要。还有其他更好的方法来抛出异常。 - dirkgently
1
这个示例程序调用了标准C++中未定义的行为,但问题是关于Microsoft SEH(结构化异常处理),而不是标准C++异常。 - bk1e
我可以理解那个。但是,a)OP的问题中从未提到SEH。b)做错事情并不会使它变得正确。 - dirkgently
所以,请停止对我的回答进行负评,因为这是完全正确的。 - dirkgently

4

如果您想使用SEH,您必须使用_set_se_translator函数和/EHa编译选项。


2

由于C++常规异常无法处理这种异常,您需要使用SEH,但它不知道应用程序并且不会继续执行堆栈展开。


0

这是未定义行为:

One* p = NULL;
p->x = 10;

此时,应用程序可以自由崩溃而不需要展开堆栈。
如果您想测试堆栈展开,请将其替换为:

 throw 42; // Life the Universe and Everything thrown away

你不应该动态分配所有的对象,这是C++,不是Java!

int main() 
{
    Test    pTest;   // Note the lack of new!

    pTest.OnRecvBuffer();

    OutputDebugString("Test");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接