C++异常处理解析2: 异常安全(内存泄露, 空指针等问题)

前言:

C++异常安全是针对C++异常处理带来的可能的隐患(内存泄露, 空指针等)而言的, 我们知道异常一旦发生, 程序就会转移控制权, 如果在转移控制权的之前, 没有妥善处理, 比如忘记释放内存, 空指针等, 会造成严重的未定义行为或者资源泄露(内存泄露, 空指针等). 所谓异常安全, 就是为了保证即使是发生了异常, 这些类似的未定义(内存泄露, 空指针等)行为也不会发生.

C++异常安全概念:

我们写程序的时候往往习惯按照假设程序正常运行的行为写代码, 管理资源等. 有时候也会写错误检测和处理的代码, 但是在这两个地方重叠时候, 也就是错误发生的时候的资源管理往往是容易被忽视的(下面马上会给出两个例子, 内存泄露问题和空指针未定义行为问题).

异常安全是这么一个概念: 这个是指, 即使发生异常, 程序也能正确操作(异常发生以后要杜绝一切未定义的行为, 包括空指针, 内存泄露等, 即使异常发生, 那么相关实例还是应该保持有效的状态).

C++异常安全要求:

C++异常安全一般有四个等级的要求(异常安全等级由低到高): 1. 没有任何异常安全保证, 也就是异常一旦发生, 可能造成程序行为的未定义; 2. 基本保证, 也就是异常发生的时候, 程序的行为还是合法的, 状态也都是有效的, 行为是有定义的, 但是程序实例的状态有可能改变(仍旧合法) 3. 强保证(回滚保证), 这个等级就要求异常一旦发生然后进行处理了以后, 要么一次性全部成功, 要么就回滚到异常钱的原始状态(程序状态和异常发生以前一模一样). 4. 保证不会有任何一方的发生.

这里面1是最不安全的, 不可取. 4基本上等级最强, 但是一般情况下不可能满足. 所以异常安全往往在2和3这两个等级间取舍. 等级3有可能会有额外的负担, 资源消耗等. 具体情况根据程序逻辑和实际情况判断取舍.

C++异常安全举例, 避免内存泄露:

C++异常安全其中一条重要的惯例, 是需要保证 如果发生异常, 被分配到的任何资源都适当地得到释放.  这个情况一般发生在动态分配内存的时候, 比如我程序里面有一段代码, 在第20行的时候首先动态分配了内存给一个指针p, 正常运行的话, 中间有一些处理代码, 然后到第40行delete [] p 释放内存, 程序正常运行的话没有问题, 但是要是在第20行到40行之间的代码出现了异常, 程序控制权转移给上级调用程序的时候, 这样的代码就有问题了, 此时, 作用域等效于已经到达了当前函数的结束, 所有局部变量或者实力都会调用自身的析构函数进行释放资源, 但是对动态分配内存的实例来讲, 因为是直接异常跳转, 虽然作用域结束, 但是没有执行到delete进行手动释放, 这块动态内存将造成内存泄露.

那么比较好的保证这一类内存资源不泄露的异常安全的技术成为“资源分配即初始化”(参考RAII). 对于这句话“资源分配即初始化”我自己是这么理解的, 我们要进行资源分配, 保证异常安全的做法不是普通的动态分配一块内存, 而是等效的初始化一个资源管理类的实例. 这就是所谓的“资源分配即初始化”, 也就是把资源分配等效的用初始化资源管理类来替代. 那么这里又提到了资源管理类, 我们解释一下资源管理类以及“资源分配即初始化”到底好处在哪里. 基本上这点要求我们设计一个资源管理类统一的管理资源的分配和释放, 更具体的, 利用构造函数分配资源, 利用析构行数释放资源. 这样做的好处呢, 是资源管理类本身是一个自动的局部对象, 不管是因为异常发生还是正常的程序运行到了改局部对象的作用域的结束的时候, 这个类的析构函数都会被调用从而保证了资源的释放, 避免了内存泄露问题. C++里面提供了RAII的auto_ptr类, 就是一个资源管理类, 行为雷系指针. 我们这里就不深入研究它了.

C++异常安全举例, 避免空指针:

C++异常安全的另一个常见的管理就是需要避免空指针. 这个情况的发生往往是我们在动态分配内存的时候发生了异常. 比如我们要分配p = new int[100], 这个时候要是内存不够, 那么就发生bad_alloc异常, p指针是空的NULL. 这个时候如果后面的代码依赖于p的未定义行为, 这样很容易导致程序的崩溃. 一个有效的避免空指针的做法就是, 在赋值之前就知道内存的分配是成功还是失败, 同样可以利用我们的资源管理类. 管理动态分配的内存, 如果分配成功, 那么将内存块的指针赋值给p, 如果失败, 那么抛出异常, 程序在p赋值前转移了控制权,此时p的值是不会改变的. 这样做就使得程序更加鲁棒(异常发生的时候, p的状态没有改变, 也没有产生未定义行为).

结束语:

C++异常安全是针对C++异常处理带来的可能的隐患而言的, 这里我们主要讨论了C++异常安全的概念, 四个C++异常安全等级要求, 以及避免内存泄露和空指针的异常安全问题举例。  其中涉及到了资源管理类, “资源分配即初始化”等保证C++异常安全的技术.

(全文完,原创文章,转载时请注明作者和出处)


(转载本站文章请注明作者和出处 烟客旅人 sigmainfy — http://www.sigmainfy.com,请勿用于任何商业用途)

Written on September 28, 2013