学习 CleanupStack

来源:互联网 发布:淘宝不能用菜鸟驿站 编辑:程序博客网 时间:2024/05/22 23:27

听说开发Symbian程序,首要的也是最重要的之一,就是搞清楚CleanupStack
CleanupStack 用起来貌似很简单,我写了如下代码:

假设我有一个叫 CFoo的类

我不禁疑问为什么要这样?Windows程序从来没有这样的代码,DOS都没有,Linux貌似也没有。
Symbian这样做是为什么?相信所有开始学习Symbian的人都会有这样的疑问。

我看了一下Symbian OS的一些介绍和历史,了解到Symbian OS从开始就是针对移动设备,主要是手机PDA所
设计开发的操作系统,设计上对于资源消耗,效率和安全稳定性都比较看重。
移动设备的主要特点就是,运算能力差,内存小,需要长时间运行,有时需要长达N年。
Symbian还是一个多任务系统,多个进程共享系统内存。
Symbian还是一个开放的平台,提供开发工具和接口,允许任何人为它开发软件。

在这样的情况下,内存泄漏是一种需要极力避免的错误,由于运行时间长,哪怕一点点内存泄漏也会
随着时间增长吞噬掉系统的本来就不多的内存,从而造成系统崩溃。


=====================回忆的分割线==========================================

我回顾了一下微软体系下是怎么解决内存泄漏的

C语言时代,完全没有解决办法,一切都靠程序员自己提高警惕,小心处理所有的内存分配和释放。
那个时代,高手和一般程序员之间的差别是很大的,能够驾驭大型的程序,小心控制好内存和指针,需要
高超的技巧和丰富的经验。好在DOS也不需要太稳定,每天都重新启动N次也无所谓。

C++语言,增加了对象的概念,有结构化异常处理,给程序员提供了一种可以控制内存的方法。
我们可以把内存释放放在类的析构函数里,这样就不会被忘掉,也可以用智能指针模板类比如 std::auto_ptr来
包装指针。如果程序可能意外退出,那么我们可以用结构化异常处理,捕获异常,我们仍然有机会在程序结束前释
放掉内存。我可以用容器类来存放数据,避免自己分配和管理内存。

到了windows时代,有了进程地址空间的概念,即使一个进程意外结束,即使有大量的内存泄漏,当进程结束后
所有这个进程的内存都会被释放掉,甚至包括进程用到的内核对象和系统资源都会被释放。而且随着PC硬件的发
展,内存变得很大,一些小的内存泄漏几乎不会产生什么问题,在积累到足以导致系统崩溃前,通常进程就结束掉了。

即使如此,内存泄露仍然是个大问题,尤其是对于一些企业级应用,服务性程序,需要长时间运行,内存泄漏的
积累会造成大麻烦,所以产生了很多工具和类库,工具可以用来监测内存泄漏,帮助开发者找出问题,类库封装
了很多系统服务和内存管理,让开发者避免和内存直接打交道。

只要是人就会出错,总会在某个时候疏忽一下忘记释放。如果成本允许,那么可以靠大量的测试来防止。
以上的方法并没有从根本上解决问题,虽然我近几年已经很少出内存泄漏错误了,但也不是绝对没有。

Java的出现,是一个重大转变,delete这个关键字彻底消失了,只有new保留了下来,内存的释放被垃圾回收机制
代替了,从此开发人员不用关心内存的释放了,一切都交给系统去处理,后来的C#也是类似的机制。


===========================回归的分割线========================================

似乎有点扯远了,回到Symbian,我在想为什么Symbian没有采用以上的方法呢?
Symbian本身是使用C++编写的,开发语言也主要是C++,虽然也支持Java和其他一些脚本语言,但是有些程序必须用C++来
开发,平台的API几乎都是C++接口的。Java的虚拟机,脚本的解释器总是要用C++来编写的。所以C++是唯一的选择。
完全用Java 和 C#来开发肯定不可能。

那么提供一个类库来解决内存分配释放呢?事实上这样的类是有的,但是只能解决开发人员忘记释放的问题,对于程序
意外结束仍然没有办法。

那么异常处理呢?很遗憾,一篇文档上说在Symbian开始设计开发的时代,还是上个世纪90年代的事,那时候C++编译器还不能完全
支持C++异常处理,至少Symbian 的编译器GCC 不可以。

我估计最后Symbian 的设计者选择了一个看似不够优雅,但是却是无可奈何的选择,那就是CleanupStack。

这个方案基本是异常的机制的替代品,大致是这样的
程序创建一个 Stack(栈),分配的内存的指针会被通过调用 CleanupStack::PushL(..) 放到Stack里。
释放内存之前 调用 CleanupStack::Pop() 把指针从stack中取出。
另外我们经常会看到 TRAP(。。。) 宏,你可以简单地认为相当于 try...catch... ,出现异常的时候会获得错误码。
所以要把一些危险的函数调用放到TRAP里面。(如果以后有机会我打算详细写写TRAP)

那么如果在new 和 delete之间产生了异常,那么内存指针就会保留在stack里,系统在进程结束的时候会把Stack里面
的内存释放掉。这样保证了即使程序意外结束,也不会有内存泄漏。

这样的方案是不是足够好呢?如果开发人员忘记了PushL或者Pop怎么办?不是一样会有内存泄漏吗?
开发人员记性不好的确是个大麻烦,但是有很多工具可以帮你检测问题,比如CodeScanner PC-Lint等等,如果你连使用工具
都忘掉了,我就无语了。。。

CodeScanner是个好东西,专门为了Symbian C++开发的静态代码分析工具。可以给出详细的关于代码问题的报告。
CodeScanner:http://www.mobileinnovation.co.uk

看来我只要保证了 PushL和Pop匹配,所有的内存分配都会被记录下来,程序结束之前都会被释放掉。

 

本文转自:http://blog.joycode.com/yaodong/archive/2007/03/02/94095.joy
版权归原作者所有,如有疑问请留言。

原创粉丝点击