C++异常处理机制是很难懂的知识,长时间来我都很惧怕它
今天我就来剖析下这个机制吧!
C++的Exception Handling由三个主要的语汇组件构成:
1.一个throw子句。
2.一个或多个catch子句。
3.一个try区段。
这些是语法,记住就可以了。
我们要研究的是,发生了异常之后会伴随着发生些什么?
这里我先提出一个术语,这个术语叫unwinding the stack (栈展开)
举个例子:
void func()
{
Student *pt1;
Student stu;
pt1 = new Student;
delete pt1;
}
当执行这个函数的时候,系统会开辟一个栈帧给它,(请参考我之前的 关于函数调用时的栈结构 的blog),如果这个函数执行的时候出现异常,那么这个栈帧就会被撤销,sp堆栈指针就会回到 调用此函数的函数的栈帧,这就是所谓的unwinding the stack
那么这个行为会导致什么呢?很显然此时func的栈帧因为出现异常导致没有了,被撤销了,那么这整个栈帧的地址空间就是可被再用的。也就是说调用func函数的函数这个时候可以使用这块地址空间了。
对于异常机制里面有一句很重要的话:在每一个函数被推离堆栈之前,函数的local class object的destructor会被调用。
这是一句很重要,非常重要,异常重要的话``````省略一千字。
别以为这句话很好理解,其实意义很深。这句话的意思是说,对于内置类型系统是直接采用覆盖的策略来重新使用堆栈的,而对于类类型是要先析构这个对象之后再来覆盖堆栈的。这句话的理解对于以后Windows编程内核对象,内存管理==内容很有帮助。
我们再来分析下这个函数,看看异常可能出现在什么地方:
1.Student stu;这里可能出现异常吗?答案是可能。
如果这里出现异常,也就是Student 类的构造函数出现了异常。怎么分析它呢?
简单,如果你看过我之前的 关于函数调用时的栈结构 的blog,相信很好理解。这里就是func里调用Student 的构造函数,sp堆栈指针是指向构造函数的栈帧顶部。这里Student 构造过程中发生了异常,那没办法要unwinding the stack ,结果构造函数的栈帧要撤销掉,撤销之前要先调用析构函数析构掉local class object,也就是说你这个Student构造出来的部分现在就要被析构掉,然后回到了调用函数也就是func的栈帧里,因为这里没有try...catch块,这个异常是不会被捕捉和处理的,那么所有的函数都将被推理堆栈,知道最后调用terminate()终止程序。所以这个时候func的栈帧也同样要被撤销掉,如果调用func的函数也没有try...catch块来捕捉和处理异常,那么同样也要撤销栈帧。同样在撤销之前会先析构掉local class object。
2.pt1 = new Student;这里可能出现异常吗?答案是可能。
这里我先说明一下这个语句发生了什么?首先是new运算符操作调用库函数的new运算符来配置所需要的内存,然后调用Student的构造函数来初始化这段内存。
这样问题就明了,异常可能发生在配置内存的时候,也可能发生初始化的时候。我们一个个来分析。
如果发生在配置内存的时候,多半因为内存不够造成,这种情况的异常同样导致unwind the stack最后调用terminate()终止程序。分析同第一项。这里有些同学可能怀疑内存泄漏。可能认为发生异常之前已经有部分内存被申请配置。这里我告诉你是不会的!内存泄漏是因为内存上的对象没有被析构,如果被析构内存自然会被回收,我们平时用的delete运算的作用就是调用析构函数析构对象来回收内存的,然而这里根本就没有对象存在于这段内存中,自然不需要析构,更谈不上内存泄漏。也就是说new运算符抛出异常之后内存申请失败,这个时候将unwind the stack最后调用terminate()终止程序,至于Student的构造函数根本是没有机会执行的。
接着我们来分析下,如果异常发生在内存初始化的时候会怎么样?也就是说Student 的构造函数内抛出异常。分析同第一项。构造函数的栈帧要撤销掉,撤销之前要先调用析构函数析构掉local class object,也就是说你这个Student构造出来的部分现在就要被析构掉 也就是说堆上构造出来的部分被析构掉了,那么堆内存就要被回收了,所以也同样不会存在内存泄漏。
void func(void *arena)
{
Student *pt1;
Student stu;
pt1 = new Student;//1
try
{
smLock(arena);
//add what you want do 2
}
catch(...)
{
smUnLock(arena);
delete pt1;
throw;
}
delete pt1;
}
我们将函数加上try...catch语句块,顺便对一块共享内存进行locking和unlocking操作。
1和2的位置是可能发生异常的位置,对于1位置发生异常就不再讨论了
如果异常发生在位置2,这个时候会造成内存泄漏和资源泄漏吗?答案是不会的!2位置抛出异常之后被抓到了,然后调用了smUnLock和delete操作将资源解锁,将内存释放了。有的同学会问,那再执行delete pt1;岂不是会出错?其实你该思考下,会不会执行最后的delete pt1;语句。答案是不会的,因为这个异常虽然被捕捉到了但是并没有被处理,而是再次抛出,交给了上层的catch语句(所谓上层就是指调用此函数的函数的语句块里)来处理,所以这个时候func的栈帧同样要被撤销,撤销之前调用析构函数析构掉local class object 。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
C++的异常处理机制是博大精深,此blog论述只是冰山一角,有兴趣的朋友可以搜索SEH,EH机制。