C++异常和结构化异常的区别

25

有人能解释一下C++异常和MFC中结构化异常的区别吗?

6个回答

23

实际上有三种机制:

  • C++异常,由编译器实现(try/catch
  • Windows提供的结构化异常处理(SEH)(__try/__except
  • MFC异常宏(TRYCATCH),建立在SEH/C++异常之上 - 另请参阅TheUndeadFish的评论)

C++异常通常在堆栈展开期间保证自动清理(即运行局部对象的析构函数),而其他机制则不会。

只有在显式抛出时,C++异常才会发生。结构化异常可能会因许多操作而发生,例如由于未定义的行为、向API传递无效指针、卸载内存映射文件的备份存储等等。

MFC引入异常宏以支持即使编译器没有实现异常也可以使用。


3
C++异常不需要保证清理工作。请使用/EHa编译。 - Hans Passant
1
MFC在Watcom C/C++ 10上工作过。不知怎么的,没有向导之类的东西。 - peterchen
2
@Hans - 嗯,真的吗?/EHa与/EHs只影响C++异常处理程序是否捕获结构化异常。我非常确定C++标准保证为C++异常进行清理(将其定义为实现定义毫无意义,因为必要的代码将根本不同)。 - peterchen
4
尽管在VC支持C++异常之前(我想是这样),MFC的异常使用了不同的方法,但现在MFC的异常都建立在C++异常的基础上。实际上,可以很容易地从MFC宏转换为C++的try/catch语句,如此链接所述:http://msdn.microsoft.com/en-us/library/19z28s5c.aspx。因此,在底层,只有两种不同类型:C++异常和SEH。 - TheUndeadFish
1
@TheUndeadFish:感谢更新,我仍然希望将其列出,因为它是第三个语法和规则集,用于处理异常。 - peterchen
显示剩余3条评论

14

这是一个 MSVC++ 的重要实现细节,但在 Windows 上,C++ 异常也是 SEH 异常。异常代码为 0xE04D5343(最后三个字节为“MSC”)。所有常规的 SEH 支持都用于展开堆栈、运行自动清理代码,并过滤异常以选择适当的 catch 子句。如此处所示

在过滤器表达式中获取抛出的异常对象是 CRT 添加的管道,超出了 SEH 提供的范畴,这必然是 C++ 特有的。

另外一个实现细节是 /EH 编译器设置。默认值 (/EHsc) 允许编译器优化生成的代码并抑制运行自动清理所需的异常过滤器。如果它可以看到没有任何发射的 C++ 代码可能会引发异常。要为 SEH 异常获得自动清理,必须使用 /EHa 进行编译,以抑制此优化。

将 C++ 异常与 SEH 结合的一种策略是使用 _set_se_translator(),以便您可以将 SEH 异常转换为 C++ 异常。尽管捕获 SEH 异常通常不明智,它们几乎总是很麻烦的。通常会选择使用 __try/__catch,如链接的答案所示。


__try/__except 块是否能捕获所有 SEH 异常以及所有 C++ 异常? - Alexandru
还需要注意的是,根据MSDN的说法,_set_se_translator()仅在具有try块的堆栈上每个函数调用中调用一次,并且必须在每个线程上设置。 - Alexandru
这个答案和Doug的回答是唯一可接受的答案;其他答案没有明确说明C++异常仅仅是添加的功能和一个运行在Windows内核底层异常处理机制之上的C++抽象。而该底层机制本质上是SEH。 - Lewis Kelsey
需要知道的关键是,C++异常只能捕获抛出的对象,而不能捕获运行时错误,例如分段错误,只有SEH异常过滤器可以用于捕获这些错误,并且在线程堆栈的基本函数处最终会有一个SEH UnhandledExceptionFilter。 - Lewis Kelsey
实际上,在SEH中,C++异常还有另一个代码:0xE06D7363(小写的“msc”),我刚刚用我的自定义SEH处理程序捕获了其中一个。这里有更多相关信息。而且,这是Raymond Chen的文章 - ScienceDiscoverer

8

C++异常是C++编程语言的一个特性。结构化异常是Windows操作系统的另一个概念。这两者使用类似的语法,但在技术上有所不同。Windows结构化异常不仅可以与C++一起使用,还可以与C等其他语言一起使用。

有时候统一处理两种异常的解决方案是:在Windows应用程序中,您可以提供一个处理程序函数,它捕获所有结构化异常并抛出一个由您定义的C++异常。


6

两者都提供了在错误发生时进行堆栈展开的机制。

结构化异常是由Windows提供的,由内核提供支持。如果您访问无效的内存位置等操作,则Windows会引发此类异常。它们还用于支持自动堆栈增长等功能。它们通常不单独使用,但C ++,.NET和类似语言中的语言异常通常建立在其之上。您可以使用特殊关键字,例如__try__catch来处理这些异常。但是,处理它们相对困难且容易出错,因为您可能会破坏自动堆栈扩展等功能,以及潜在地破坏C ++语言异常。

C++异常由C++语言指定。被抛出和捕获的数据类型是C++对象(包括原始类型的可能性)。编译器和运行时在基础结构化异常机制之上实现这些。如果您使用C++语言的trycatchthrow关键字,则会得到这种异常。

SEH异常具有比C++异常更多的功能,例如支持恢复和所谓的“向量”处理程序(接收异常通知,但不一定防止堆栈展开),但除非您明确知道要使用它们,否则我会避免使用它们。它们最常见的用途可能是在程序执行非法或未定义操作时编写崩溃转储,使用MiniDumpWriteDump


2

C++异常将跨平台工作。不幸的是,SEH会严重限制可移植性(除非在不同的Windows版本之间)。

另外,SEH似乎捕获了许多本地Windows异常(例如访问冲突,指定了无效的句柄等)。


1
在Windows上,C++异常和SEH基本上是相同的东西。C++异常只是可能被抛出的许多SEH异常之一。有些异常来自硬件,有些来自软件。C++异常是由软件触发的异常。Raymond Chen写了一篇非常棒的博客文章,在这里,可以帮助你深入了解SEH异常以及C++异常是如何建立在它们之上的。
这是我根据上述文章的实际实现。我真的很震惊,经过12年和:

请注意,此信息属于实现细节类别。不能保证该方法将来仍能正常工作,因此不要编写依赖它的代码。这只是一个调试提示。

到2022年最新的Windows 10上仍然能够正常工作!我的解决方案还提供了从异常中提取的完整信息,因此您甚至不需要调试器,只需使用__try\__except捕获异常并使用此函数打印任何异常的信息:
#define EXCEPTION_CPP_LOWERCASE 0xE06D7363
#define EXCEPTION_CPP_UPPERCASE 0xE04D5343

void pexcept(DWORD c, _EXCEPTION_RECORD *er)
{
    cout << "--------------------------------------------------------------------" << endl;
    switch(c)
    {
    case STILL_ACTIVE:
        cout << "STILL_ACTIVE" << endl;
        break;
    case EXCEPTION_ACCESS_VIOLATION:
        if(er->ExceptionInformation[0] == 0)
        {
            cout << "READ ";
        }
        else if(er->ExceptionInformation[0] == 1)
        {
            cout << "WRITE ";
        }
        else if(er->ExceptionInformation[0] == 8)
        {
            cout << "DEP "; // Data Execution Prevention
        }
        cout << "ACCESS_VIOLATION" << endl;
        break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
        cout << "DATATYPE_MISALIGNMENT" << endl;
        break;
    case EXCEPTION_BREAKPOINT:
        cout << "BREAKPOINT" << endl;
        break;
    case EXCEPTION_SINGLE_STEP:
        cout << "SINGLE_STEP" << endl;
        break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
        cout << "ARRAY_BOUNDS_EXCEEDED" << endl;
        break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
        cout << "FLOAT_DENORMAL_OPERAND" << endl;
        break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
        cout << "FLOAT_DIVIDE_BY_ZERO" << endl;
        break;
    case EXCEPTION_FLT_INEXACT_RESULT:
        cout << "FLOAT_INEXACT_RESULT" << endl;
        break;
    case EXCEPTION_FLT_INVALID_OPERATION:
        cout << "FLOAT_INVALID_OPERATION" << endl;
        break;
    case EXCEPTION_FLT_OVERFLOW:
        cout << "FLOAT_OVERFLOW" << endl;
        break;
    case EXCEPTION_FLT_STACK_CHECK:
        cout << "FLOAT_STACK_CHECK" << endl;
        break;
    case EXCEPTION_FLT_UNDERFLOW:
        cout << "FLOAT_UNDERFLOW" << endl;
        break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
        cout << "INTEGER_DIVIDE_BY_ZERO" << endl;
        break;
    case EXCEPTION_INT_OVERFLOW:
        cout << "INTEGER_OVERFLOW" << endl;
        break;
    case EXCEPTION_PRIV_INSTRUCTION:
        cout << "PRIVILEGED_INSTRUCTION" << endl;
        break;
    case EXCEPTION_IN_PAGE_ERROR:
        if(er->ExceptionInformation[0] == 0)
        {
            cout << "READ ";
        }
        else if(er->ExceptionInformation[0] == 1)
        {
            cout << "WRITE ";
        }
        else if(er->ExceptionInformation[0] == 8)
        {
            cout << "DEP "; // Data Execution Prevention
        }
        cout << "IN_PAGE_ERROR" << endl;
        cout << "DATA VADDRESS:       " << er->ExceptionInformation[1] << endl;
        cout << "NTSTATUS CAUSE:      " << er->ExceptionInformation[2] << endl;
        break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
        cout << "ILLEGAL_INSTRUCTION" << endl;
        break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
        cout << "NONCONTINUABLE_EXCEPTION" << endl;
        break;
    case EXCEPTION_STACK_OVERFLOW:
        cout << "STACK_OVERFLOW" << endl;
        break;
    case EXCEPTION_INVALID_DISPOSITION:
        cout << "INVALID_DISPOSITION" << endl;
        break;
    case EXCEPTION_GUARD_PAGE:
        cout << "GUARD_PAGE_VIOLATION" << endl;
        break;
    case EXCEPTION_INVALID_HANDLE:
        cout << "INVALID_HANDLE" << endl;
        break;
    // case EXCEPTION_POSSIBLE_DEADLOCK:
        // cout << "POSSIBLE_DEADLOCK" << endl;
        // break;
    case CONTROL_C_EXIT:
        cout << "CONTROL_C_EXIT" << endl;
        break;
    case EXCEPTION_CPP_LOWERCASE:
    case EXCEPTION_CPP_UPPERCASE:
        cout << "CPP_EXCEPTION" << endl;
        cout << "PARAMS NUM: " << er->NumberParameters << endl;
        for(ui64 i = 0; i < er->NumberParameters; ++i)
        {
            cout << dec << "PARAM" << setw(2) << left << i << " ";
            switch(i)
            {
            case 0:
                cout << "      SOME INTERNAL VALUE";
                break;
            case 1:
                cout << "    POINTER TO THROWN OBJ";
                break;
            case 2:
                cout << "    POINTER TO OBJECT INF";
                break;
            case 3:
                cout << "DLL/EXE THROWER HINSTANCE";
                break;
            }
            
            cout << ": 0x" << hex
                << uppercase << er->ExceptionInformation[i] << endl;
        }
    {
        ui64 hinst = er->ExceptionInformation[3];
        DWORD *obj_inf = (DWORD *)er->ExceptionInformation[2];
        cout << hex << uppercase << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << obj_inf[2] << endl;
        cout << obj_inf[3] << endl;
        cout << "-----------------------------" << endl;
        obj_inf = (DWORD *)(hinst + obj_inf[3]);
        cout << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << "-----------------------------" << endl;
        obj_inf = (DWORD *)(hinst + obj_inf[1]);
        cout << obj_inf[0] << endl;
        cout << obj_inf[1] << endl;
        cout << "-----------------------------" << endl;
        ui64 *class_inf = (ui64 *)(hinst + obj_inf[1]);
        cout << class_inf[0] << endl;
        cout << class_inf[1] << endl;
        cout << class_inf[2] << endl;
        char *class_name = (char *)(class_inf + 2);
        
        cout << class_name << endl;
    }   
        
        cout << "CURRENT EXE HIN:  " << hex << uppercase << "0x" << GetModuleHandle(NULL) << endl;
        cout << "UCRTBASE HIN:     " << hex << uppercase << "0x" << LoadLibrary(L"ucrtbase.dll") << endl;
        cout << "VCRUNTIME140 HIN: " << hex << uppercase << "0x" << LoadLibrary(L"vcruntime140.dll") << endl;
        cout << "STD MSVCP140 HIN: " << hex << uppercase << "0x" << LoadLibrary(L"msvcp140.dll") << endl;
        break;
    default:
        cout << "UNKNOWN_EXCEPTION [" << hex << uppercase << c << "]" << endl;
    }
    
    cout << "CONTINUE EXECUTION:  "
        << (er->ExceptionFlags == EXCEPTION_NONCONTINUABLE ? "NOT " : "" )
        << "POSSIBLE" << endl;
    
    cout << "INSTRUCTION ADDRESS: 0x" << hex << uppercase
        << er->ExceptionAddress << endl;
        
    if(er->ExceptionRecord != NULL)
    {
        cout << "CHAINED EXCEPTION!" << endl;
        pexcept(er->ExceptionRecord->ExceptionCode,
            er->ExceptionRecord);
    }
    else
    {
        cout << "--------------------------------------------------------------------" << endl;
    }
}

以下是您的输出示例(手工形成的ASCII图):

--------------------------------------------------------------------
CPP_EXCEPTION
PARAMS NUM: 4
PARAM0        SOME INTERNAL VALUE: 0x19930520
PARAM1      POINTER TO THROWN OBJ: 0x14FA30
PARAM2      POINTER TO OBJECT INF: 0x7FFE68B246B0 ---->
PARAM3  DLL/EXE THROWER HINSTANCE: 0x7FFE68AC0000
     +-----+
 --> |0    |
     +-----+
     |4D0C |
     +-----+
     |0    |
     +-----+     +-----+
     |646D0|---->|3    |
     +-----+     +-----+     +-----+
                 |646F0|---->|0    |
                 +-----+     +-----+     +----------------+
                             |84578|---->|0               |
                             +-----+     +----------------+
                                         |7FFE68B1C218    |
                                         +----------------+
                                         |5F74756F56413F2E|
                                         +----------------+
                                    ..... _ t u o V A ? .
                                    
.?AVout_of_range@std@@
CURRENT EXE HIN:  0x0000000140000000
UCRTBASE HIN:     0x00007FFE99D50000
VCRUNTIME140 HIN: 0x00007FFE95040000
STD MSVCP140 HIN: 0x00007FFE68AC0000
CONTINUE EXECUTION:  NOT POSSIBLE
INSTRUCTION ADDRESS: 0x00007FFE99F54FD9
--------------------------------------------------------------------

正如您所看到的,lust number 不是指针,而是异常的实际类名(在 Little Endian 中解释为数字)。异常的 HINSTANCE 参数与标准 C++ 库相同(在我的情况下是 msvcp140.dll),因此很明显这个异常的来源。


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