关于异常
1、C++标准异常:
c++提供了一种异常的处理方法:try()catch();
不知道小伙伴有没有这样的疑惑:1)为何我们要捕获异常?2)何时使用异常处理?3)异常处理会不会降低程序性能?4)如何处理异常呢?
1)为何需要捕获异常?何时抛出异常?
异常处理就是处理程序中的错误。
异常,通俗来说,对于被调用的函数,如果说因为调用者所引发的错误,且无法继续执行时,需要通知调用者,发生错误无法执行了;让上层的逻辑去处理它。
异常处理,对于程序来说,可以知道什么出错了?哪里出错了?以及为什么出错了?这可以作为程序的一大调试手段。
而且,作为一个底层函数,出了错误,如果需要return,则需要定义好一系列的错误类型,让上层的调用程序知道具体的错误;当存在大量的if else时,这些错误处理代码与正常的业务代码就会纠缠在一起,为了编译开发维护,此时你就该使用try catch处理异常了。
异常处理从另一个角度看,需要调用者自己处理可能发生的错误,比如说除数为0,就是需要调用者自己排除才对,如果通过if语句检查,并return 错误码,如果调用者没有检查返回值呢?所以使用异常处理,可以强制方法调用值处理可能发生的错误。
2)何时使用异常处理呢?
这里引用C++之父的话,一个库的作者可以检测出发生了运行时错误,但一般不知道怎么处理它们。而异常的基本目的就是处理这些错误,对自身无法处理的错误,抛出一个异常,让它的调用者能够处理这个问题。
从前面的描述,大致可以认为:对于可预测的异常情况,像被除数为0是可以用if、else的进行处理,使用exception是为了,让if/else 纯粹的去操作业务逻辑 ,不会应为过多的逻辑分支影响代码的可读性和可维护;
对于不可预测的异常情况,比如读取文件,序列化对象,面对种种可能引发异常的情况。此时exception 提供了统一的处理方式。
3)异常处理会不会降低性能?
那么异常处理会不会减低性能呢?从有关资料的测试中来看,C++的 try部分代码运行并不会有明显的性能降低问题;当出现异常,catch异常是会消耗一点点时间的,但在正常运行时,并没有造成什么性能上的损耗。
4)如何处理异常?
如何处理异常,即异常的基本语法和使用注意项:
<1>首先是抛出与捕获异常:
抛出使用throw,捕获使用try.....catch,举例如下:
https://blog.csdn.net/daheiantian/article/details/6530318?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
具体这里不再赘述。
2. Win32系统提供的SEH(结构化异常处理)
所谓的结构化异常,通俗来说就是当程序出现错误时,操作系统会调用用户定义的一个回调函数:
EXCEPTIO_DISPOSITION
__cdecl _except_handler( struct _EXPECTION_RECORD *ExceptionRecord, void * EstablisherFrame, sturct _CONTEXT *ContextRecord, void *DispatcherContext);
值得注意的是,此种方法未windows提供,并不能夸平台。
语法介绍:
1)try-except语句:
try-except语句可以使被保护的代码出现异常时候进行控制,其中__except表达式括号内的值可以有以下三种值:
EXCEPTION_CONTINUE_EXECUTION(-1)异常被忽略,在发生异常的位置继续执行。
EXCEPTION_CONTINUE_SEARCH(0)无法处理异常。继续在调用链中寻找能够处理异常的代码。
EXCEPTION_EXECUTE_HANDLER(1)使用__except块里的代码处理异常,然后在__except块后继续执行。
__try{
// guarded code
}
__except ( expression ){
// exception handler code
}
2)try-finally语句
在try-finally语句中,不管__try块是正常结束还是异常结束,__finally块的代码总是会被执行。但在__try块中发生异常时,需要有异常处理程序能否处理这个异常,例如外部嵌套的try-except语句,否则程序将会终止,try-finally语句并不会处理异常。当外部有except异常处理程序,则会优先执行__finally块,再执行异常处理程序。
in
t main(){
__try
{
__try
{
RaiseException(0xc0000005, 0, 0, 0);
}
__finally
{
cout << "__finally" << endl;
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
cout << "__except"<< endl;
}
system("pause");
}
此语句有个非常好的用法就是:在__try块中分配资源,在__finally块中判断资源不为空时释放它,这样可以保证所有资源都能被安全的释放。
3)__leave语句
由于在__try块中使用return、goto、continue、break退出__try块时会产生额外的开销,这里我们可以使用__leave退出__try块,以避免不必要的开销。
这里还需注意异常相关的编译选项:
/EHs /EHsc(默认选项)
try-catch只处理C++异常,不捕捉结构化异常
try-except处理C++异常和结构化异常
在使用try-except捕捉到结构化异常时,try块内的局部对象不会被自动析构
/EHa
try-catch捕捉C++异常和结构化异常
try-except处理C++异常和结构化异常
在使用try-except捕捉到结构化异常时,try块内的局部对象会自动析构
无/EH
try catch不处理异常
try-except处理C++异常和结构化异常
在使用try-except捕捉到结构化异常时,try块内的局部对象不会被自动析构
常用的一些API:
GetExceptionCode,获取异常代码,只能在异常过滤表达式或异常处理块中调用。
GetExceptionInformation,获取异常相关信息,只能在异常过滤表达式中使用,返回的数据保存在转移到异常处理块中时不再可用。
RaiseException,触发一个异常,自定义异常代码最好以0xE开头(例如0xE0000001),以避免和Windows异常代码冲突。
AbnormalTermination,如果__try块正常结束则返回0(包括__leave导致的结束),否则返回非0(包括return,goto,continue,break导致的结束),只能在__finally块中调用该函数。
3. 异常时保存关键信息minidump
相信使用windows的小伙伴都接触过windows蓝屏问题,windows蓝屏时,如果系统设置了内存转储,则会将奔溃时的内存数据存储在文件中,用于后续的蓝屏问题分析排错。
这种内存转储文件,报错了故障时所有程序的数据与状态,我们可以通过windbg分析文件,分析排查出具体哪个应用、驱动等导致的操作系统奔溃问题。
这种转储文件一般都是非常多,如果是完全内存转储,一般会生成和PC的内存一样的转储文件,文件一般存储在C:\\windows目录下;因此如果操作系统的C盘空间不足,也会导致生成转储文件失败。
那如果我们的应用程序奔溃时,是否也可以生成奔溃转储文件呢?
自Windows XP以来,微软提供了一种”minidump”的奔溃转储技术,它保存了故障进程的所有线程的调用堆栈,以及局部变量的值,这种dump文件相比于系统的转储文件占用很少的字节,通常只有几K字节。通过触发这种奔溃转储,开发人员可以尽可能的获取奔溃发生时,程序运行的各种信息。
那如何在自己的程序中集成minidump,以便程序异常是,可以将异常时刻的故障信息保存下来,方便后续的排查?
好在已经有现成的函数API供我们使用了,我们可以通过DbgHelp函数组提供的函数来创建自己程序的minidump:
1)MiniDumpWriteDump函数
BOOL MiniDumpWriteDump(
HANDLE hProcess, //进程句柄
DWORD ProcessId, //进程ID
HANDLE hFile, //创建的文件句柄
MINIDUMP_TYPE DumpType, //MINIDUMP_TYPE
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);
minidump中应该包含哪些数据?主要就是取决于DumpType变量。
常用的DumpType值:
MiniDumpNormal:值为0,表示一组基础的数据集合。
MiniDumpWithFullMemory:值为2, 表示包含进程地址空间素有可读页面的内容。
这样我们可以实现一个基础的写minidump方法:
void CreateMiniDump( EXCEPTION_POINTERS* pep )
{
// Open the file
HANDLE hFile = CreateFile( _T("MiniDump.dmp"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if( ( hFile != NULL ) && ( hFile != INVALID_HANDLE_VALUE ) )//创建文件
{
// Create the minidump
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;//指向故障信息
mdei.ClientPointers = FALSE;
MINIDUMP_TYPE mdt = MiniDumpNormal;//指定minidump收集的信息
BOOL rv = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, mdt, (pep != 0) ? &mdei : 0, 0, 0 );
if( !rv )
_tprintf( _T("MiniDumpWriteDump failed. Error: %u \n"), GetLastError() );
else
_tprintf( _T("Minidump created.\n") );
// Close the file
CloseHandle( hFile );
}
else
{
_tprintf( _T("CreateFile failed. Error: %u \n"), GetLastError() );
}
}
2)MiniDumpCallback函数
如果MINIDUMP_TYPE不能满足我们定制minidump内容的需要,我们可以使用MiniDumpCallback函数。这是一个用户定义的回调函数,MiniDumpWriteDump会调用它,让用户来决定是否把某些数据放到minidump中。通过这个函数,我们可以完成这些功能。
具体可见:https://www.cnblogs.com/lidabo/p/3635960.html
基于上述API,我们就可以在自己的代码中集成minidump了:
可以使用Windows的SEH的API:SetUnhandledExceptionFilter,设置一个异常过滤器,这样每当一个SEH发生时,如果系统找不到合适的处理代码,就会调用设置的过滤函数,进行处理。
在我们设置的异常处理函数中,我们就可以使用上面的minidump,保存当前程序的dump文件。