c++异常机制分析
本文主要参考《C++异常机制的实现方式和开销分析》
栈帧(stack frame)
栈帧这个概念在c/c++里面是一个很重要的,是走进反汇编,了解代码底层结构的第一大步。
栈帧是编译器实现函数调用过程的数据结构,编译器正是通过该结构,通过寄存器在栈空间存取数据实现了函数的调用。
另外c++的异常机制正是通过在栈桢内部添加相关数据结构来实现的,因此先了解栈帧这一概念就显得更为重要了。
图1 栈帧结构图
如图1左图显示的是简略的栈帧结构,可以看到栈内依次压入了:
nArg1、nArg2
,此为传入的形参(实参默认应从右至左入栈);pRetAddr
,该指针指向执行完该函数代码之后要返回的下一段代码的地址;Caller's EBP
,该指针指向上一级栈帧的EBP(基底指针寄存器)地址,在该函数执行完之后将EBP地址重置。
最后则是函数内部的局部变量依次入栈,这些结构实现了:传参,函数跳转,回跳,栈控制等功能,更多的相关概念可以参考blog
c++异常处理机制
c++的异常处理,主要包括了栈回退(stack unwind)机制和异常捕获机制,通过在栈帧内的相关数据结构来实现。
数据结构
上右图是引入了异常处理之后的栈帧结构,可以看到加入了一个新的数据结构:
piRrev
,指向上一级异常的地址,实现异常向上抛出的功能,由此可见,该结构是一个单向链表;piHandler
,指向完成异常处理以上两个机制必须的数据结构;nStep
,跟踪函数内部所有局部对象的构造和析构,每新生成一个新的对象就会自加1。
栈回退机制
在c++异常处理中,是没有finally:
段的,但是依然能够自动实现触发异常后,对象得到正确析构,内存能够正确回收。依靠的就是该机制。
图2 栈回退表数据结构
该机制依靠的是piHandler
指向的EHDL
结构体内的由UNWINDTBL
结构体类型组成的数组,该类型结构体有三个成员如上图2。该数组包括函数内所有对象需要回退的相关信息,包括:
nNextldx
,下一个要执行析构的对象对应的数组下标;pfnDestroyer
,要执行的析构函数;pObj
,对应需要执行析构的对象的this指针。
可以预见到,一旦发生异常,就会根据数组下标和所指的下一个需要执行析构的对象的下标nNextldx
,实现自下而上地对所有UNWINDTBL
结构体内对应的对象pObj
执行对应析构函数pfnDestroyer
。
异常捕获机制
这部分主要是介绍c++在catch到异常后如何执行对应代码。
图3 try表数据结构
在piHandler
指向的EHDL
结构体内有另一个重要的TRYBLOCK
结构体数组,里面存放则是该机制所需的数据。如上图3,该结构体内包括三个变量两个部分:
nBeginStep
、nEndStep
,在本章开始介绍,,这两个整数则记录try
段开始和结束时nStep
的数值;tblCatchBlocks
,该指针指向另一个结构体数组,保存所有catch块相关信息。piType
,异常类型。pCatchBlockEntry
,指向对应catch块代码的入口地址
当代码发生异常时,通过判断此时刻nStep
的值是否在[nBeginStep,nEndStep)
内来确定,该异常是否处在该try
段内。如果是,则会进入tblCatchBlocks
内寻找对应的对应的异常类型piType
,并通过入口地址pCatchBlockEntry
进入对应catch块内代码。