VS原生C++单元测试断言失败触发栈展开却无法捕获C++异常原因咨询
我来给你捋捋这个事儿,其实核心原因是VS单元测试里的断言和__int2c()都不走标准C++异常那套流程,而是用了Windows特有的结构化异常处理(SEH),再加上测试框架的特殊拦截,才导致你看到这种“像异常但抓不到”的情况。
先看你的代码示例,格式化后是这样:
void func() { struct Foo { ~Foo() { // this code is executed } }; Foo foo; try { Assert::IsTrue(false); } catch (...) { // this code is not executed } // this code is not executed }
下面具体解释几个关键点:
断言底层是SEH,不是C++标准异常
VS Native Test Framework的断言函数,比如Assert::IsTrue,底层是通过Windows的SEH机制触发异常的(比如调用RaiseException或者直接触发硬件中断),而不是抛出std::exception这类标准C异常。C的catch(...)只能捕获符合C标准的异常对象,对SEH这种系统级别的异常,默认是不感知的——哪怕你开了/EHa编译选项(允许C catch捕获SEH),测试框架也会提前把这个异常截走。栈展开是SEH的行为,不是C++异常的锅
你看到局部对象Foo的析构函数被执行,是因为SEH在处理异常时也会进行栈展开,清理局部对象,这和C异常的栈展开行为刚好一致,但本质是两套不同的机制。所以会给你一种“像是抛出了C异常”的错觉,但实际上根本不是。测试框架主动拦截了异常流
单元测试框架的核心职责之一就是报告测试失败,所以当断言触发SEH异常时,框架会第一时间捕获这个异常,生成失败报告,然后直接终止当前测试函数的执行。这就导致你的catch(...)完全没机会接触到这个异常,后续代码自然也不会执行。__int2c()的行为一致的原因__int2c()触发的是INT 2C中断,这在Windows里属于SEH异常的一种触发方式,和断言的底层逻辑完全同源,所以表现出来的“栈展开但C++ catch抓不到”的行为也就一模一样了。
如果真的想捕获这类SEH异常,可以试试Windows特有的__try/__except块(注意这不是C++标准语法),比如:
void func() { struct Foo { ~Foo() { /* 析构代码会执行 */ } }; Foo foo; __try { Assert::IsTrue(false); } __except(EXCEPTION_EXECUTE_HANDLER) { // 这里可以捕获到SEH异常 } }
不过要提醒一下,单元测试框架可能还是会干预这个流程,毕竟它的目的是记录测试失败,而不是让你自行处理断言异常。
内容的提问来源于stack exchange,提问作者undermind

