SEH与C++ 异常模型在混合使用时注意情况与错误C2712、C2713
相关参考:
http://www.360doc.com/content/12/0405/15/1016783_201118203.shtml
https://blog.csdn.net/ShiQW5696/article/details/80664749
一、分割开的混合使用
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
using namespace std;
class A
{
public:
void f1() {}
void f2() { throw 888; } //抛出 C++ 异常
};
//这个函数中使用了 try-catch 处理异常,也即 C++ 异常处理
void test1()
{
A a1;
A a2, a3;
try
{
a2.f1();
a3.f2();
}
catch (int errorcode)
{
printf(" catch exception,error code:%d\n ", errorcode);
}
}
// 这个函数没什么改变,仍然采用 try-except 异常机制,也即 SEH 机制
void test()
{
int * p = 0x00000000; // pointer to NULL
__try
{
// 这里调用 test1 函数
test1();
puts(" in try1 ");
__try
{
puts(" in try2 ");
* p = 13; //导致一个存储异常
puts(" 这里不会被执行到 ");
}
__finally
{
puts(" in finally ");
}
puts(" 这里也不会被执行到 ");
}
__except (puts(" in filter 1 "), EXCEPTION_CONTINUE_SEARCH)
{
puts(" in except 1 ");
}
}
void main()
{
puts(" hello ");
__try
{
test();
}
__except (puts(" in filter 2 "), EXCEPTION_EXECUTE_HANDLER)
{
puts(" in except 2 ");
}
puts(" world ");
}
上面的例程中,虽然在同一个程序中既有 try-catch 机制,又有 try-except 机制,当然这也完全算得上 SEH 与 C++ 异常模型的混合使用 。但是,请注意,这两种机制其实是完全被分割开的,它们完全被分割在了两个函数的内部。也即这两种机制其实并没有完全交互起来,换句话说,它们还算不上两种异常处理机制真正的混合使用。
打印结果如下:
hello
catch exception, error code : 888
in try
in try
in filter 1
in filter 2
in finally
in except 2
world
二、俩者联合的混合使用
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
using namespace std;
class MyException
{
public:
MyException() { printf(" 构造一个 MyException 对象 \n"); }
MyException(const MyException& e) { printf(" 拷贝一个 MyException 对象 \n"); }
MyException & operator=(const MyException& e) { printf(" 赋值一个 MyException 对象 \n"); }
~MyException() { printf(" 析构一个 MyException 对象 \n"); }
};
class A
{
public:
A() { printf(" 构造一个 A 对象 \n"); }
~A() { printf(" 析构一个 A 对象 \n"); }
void f1() {}
// 注意,这里抛出了一个 MyException 类型的异常对象
//throw还触发了一次拷贝构造
void f2() { MyException e; throw e; }
};
//这个函数中使用了 try-catch 处理异常,也即 C++ 异常处理
void test1()
{
A a1;
try
{
a1.f1();
a1.f2();
}
// 这里虽然有 catch 块,但是它捕获不到上面抛出的 C++ 异常对象
catch (int errorcode)
{
printf("catch exception,error code:%d\n", errorcode);
}
}
// 这个函数没什么改变,仍然采用 try-except 异常机制,也即 SEH 机制
void test()
{
int* p = 0x00000000; // pointer to NULL
__try
{
// 这里调用 test1 函数
// 注意, test1 函数中会抛出一个 C++ 异常对象
test1();
puts("in try");
__try
{
puts("in try");
*p = 13;
puts(" 这里不会被执行到 ");
}
__finally
{
puts("in finally");
}
puts(" 这里也不会被执行到 ");
}
__except (puts("in filter 1"), EXCEPTION_CONTINUE_SEARCH)
{
puts("in except 1");
}
}
void main()
{
puts("hello");
__try
{
test();
}
// 这里能捕获到 C++ 异常对象吗?拭目以待吧!
__except (puts("in filter 2"), EXCEPTION_EXECUTE_HANDLER)
{
puts("in except 2");
}
puts("world");
}
打印结果如下:
hello
构造一个 A 对象
构造一个 MyException 对象
拷贝一个 MyException 对象
in filter1
in filter2
析构一个 MyException 对象
析构一个 MyException 对象
析构一个 A 对象
in except 2
world
上层的 main() 函数和 test() 函数采用 try - except 语句处理异常,而下层的 test1() 函数采用标准的 try - catch 语句处理异常,并且,下层的 test1() 函数所抛出的 C++ 异常会被上层的 try - except 所捕获到,即符合了 SEH 异常模型的规则,又同时遵循了 C++ 异常模型的规则。
注意:throw e的调用,是讲e进行抛出,所以发生了一次拷贝构造函数
疑问:
(1)为什么catch没有捕获到而__except却捕获到了?
(2)__finally中的代码为什么没有被执行?
编程时滥用 try-except 和 try-catch 在一起混用,不仅使我们程序的整体结构和语义受到影响,而且也会造成一定的内存资源泄漏,甚至其它的不稳定因素。在 C++ 程序中运用 try-except 机制,只有在顶层的函数作用域中(例如,系统运行库中,或 plugin 的钩子中)才有必要这样做,一般建议俩者不要混合使用
三、编译错误C2713
每个函数只允许一种形式的异常处理,不能在同一函数中使用结构化异常处理(__try / __除外)和C ++异常处理(try / catch)
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
int* p = 0x00000000; // pointer to NULL
__try
{
puts("in try");
// 这里是 C++ 的异常处理语法
try
{
puts("in try");
*p = 13;
puts(" 这里不会被执行到 ");
}
catch (...)
{
puts("catch anything");
}
puts(" 这里也不会被执行到 ");
}
__except (puts("in filter 1"), EXCEPTION_EXECUTE_HANDLER)
{
puts("in except 1");
}
}
VC 实现的异常处理机制,不管是 try - except 模型,还是 try - catch 模型,它们都是以函数作为一个最基本“分析和控制”的目标,也即,如果一个函数内使用了异常处理机制, VC 编译器在编译该函数时,它会给此函数插入一些“代码和信息”(代码指的是当该函数中出现异常时的回调函数,而信息主要是指与异常出现相关的一些必要的链表),因此每份函数只能有一份这样的东西(“代码和信息”),故一个函数只能采用一种形式的异常处理规则
四、编译错误C2712
不能在需要对象展开的函数中使用__try
使用 / EHsc时,具有结构化异常处理的函数不能具有需要展开(销毁)的对象。
如果调用使用__event关键字声明的方法,也会发生错误C2712 。由于事件可能在多线程环境中使用,因此编译器会生成阻止操作底层事件对象的代码,然后将生成的代码封装在SEH try - finally语句中。因此,如果调用事件方法并通过值传递类型具有析构函数的参数,则会发生错误C2712。在这种情况下,一种解决方案是将参数作为常量引用传递
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
using namespace std;
class A
{
public:
A() { printf(" 构造一个 A 对象 \n"); }
~A() { printf(" 析构一个 A 对象 \n"); }
void f1() {}
void f2() {}
};
int main()
{
__try
{
A a1, a2; //产生异常后会调用析构函数
puts("in try");
}
__except (puts("in filter 1"), EXCEPTION_EXECUTE_HANDLER)
{
puts("in except 1");
}
}
C++ 异常处理模型中,为了能够在异常发生后,保证正确地释放相关的局部变量(也即调用析构函数),它必须要跟踪每一个“对象”的创建过程,这种由于异常产生而导致的对象析构的过程,被称为“ unwind ”,因此,如果一个函数中有局部对象的存在,那么它就一定会存在 C++ 的异常处理机制(即会给此函数插入一些用于 C++ 异常处理“代码和信息”),这样,如果该函数(此时也就是main函数)中在再使用 try-except 机制,就会造成冲突,所以编译器也就报错了。
可能的解决方案:
(1)将需要SEH的代码移动到另一个函数
(2)重写使用SEH的函数以避免使用具有析构函数的局部变量和参数。不要在构造函数或析构函数中使用SEH
(3)无 / EHsc编译
五、其他C2712错误案例及解决方案
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <string>
using namespace std;
inline std::string foo() { return "abc"; }
inline int foo2() { return 612; }
class MyClass
{
public:
MyClass() { m_val = 0; }
MyClass(int param) { m_val = param; }
~MyClass() {}
int GetVal() { return m_val; }
private:
int m_val;
};
void TestTryExcept_1()
{
using namespace std;
string str = "666"; // string 是一个类,销毁对象时会调用析构函数,所以会报错
__try
{
// Do anything
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
cout << "__except" << endl;
}
// string str; // 无论放在函数里的什么位置都会导致 C2712 错误
}
void TestTryExcept_2()
{
using namespace std;
// foo()返回的是临时的string对象,
// 也就是说,调用foo()时,会生成一个临时的string变量存放返回的数据
// 本质上和TestTryExcept_1()是一样的
foo();
// 和 TestTryExcept_1() 一样使用了 string 类
// string retStr = foo();
__try
{
// Do anything
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
cout << "__except" << endl;
}
}
void TestTryExcept_3()
{
// 使用了自己定义的类也会报错,因为销毁对象时同样会调用析构
MyClass ObjA(612);
int ret = ObjA.GetVal();
__try
{
cout << "__try:" << ret << endl;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
cout << "__except" << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
TestTryExcept_1();
TestTryExcept_2();
TestTryExcept_3();
return 0;
}
修改之后的案例:
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <string>
using namespace std;
inline std::string foo() { return "abc"; }
inline int foo2() { return 612; }
class MyClass
{
public:
MyClass() { m_val = 0; }
MyClass(int param) { m_val = param; }
~MyClass() {}
int GetVal() { return m_val; }
private:
int m_val;
};
void TestTryExcept_1()
{
using namespace std;
string str = "666";
cout << "TestTryExcept_1:" << str.c_str() << endl;
}
void TestTryExcept_2()
{
using namespace std;
cout << "TestTryExcept_2:" << foo() << endl;
}
void TestTryExcept_3()
{
MyClass ObjA(612);
int ret = ObjA.GetVal();
cout << "TestTryExcept_3:" << ret << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
// 类对象的创建 不能和__try__except在同一个函数中
//using namespace std;
//string str = "main, string object";
//printf("%s\n", str);
__try
{
TestTryExcept_1();
TestTryExcept_2();
TestTryExcept_3();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
printf("__except\n");
}
getchar();
return 0;
}
上述修改后的代码还存在一个问题,就是在 main 中使用了__try __except 后,就无法创建类对象,也就是像 string 这样的类就无法使用了,要不然就会报错。所以我们再修改一下,把 main 拆成两个函数:
void TestTryExcept_all()
{
__try
{
TestTryExcept_1();
TestTryExcept_2();
TestTryExcept_3();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
cout << "__except" << endl;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
using namespace std;
string str = "main, string object";
printf_s("%s\n", str);
TestTryExcept_all();
getchar();
return 0;
}
这样将__try __except分到单独一个函数中,就可以解决问题了
相关参考:
六、try catch模型与__try __except模型不同点之一
try catch不能捕获内存访问错误,__try __except可以捕获内存访问错误,如 int* i=NULL; *i=1; 这俩行代码