Symbian 操作系统 C++工程中消除内存泄露

       Symbian操作系统中,编程框架在调试模式下要确保跟踪内存分配,在编译过程中如果终止时出现内存泄露,这个应用程序会被告警。应用程序告警是一种直接和迅速的方式去使开发人员意识到存在内存泄露问题需要被修正。内存泄漏问题需要马上被修正,为了以最低代价完成这个工作,有很多内容需要介绍。另外,开发过程中,在可选择的范围内,你可以自由选择工具来保证手动完成这样的内存检测。你可以在一些代码块的前后设置标记来确保它不泄露内存。实际上,这是编程框架所做的事情,它将标记设置在一个应用程序开始和结束的位置。如果你在Symbian操作系统的C++应用框架以外开发软件,则必须采用这些手动的机制。

这些在调试过程中出现的工具是内存分配框架的调用,并且为方便调用已经被封装到宏中(定义在e32def.h中)。如:__UHEAP_MARK  和 __UHEAP_MARKEND。这两个调用合在一起所要做的工作是用来检验一个码块分配内存和释放内存的对应关系。如果出现不对应的地方,他们向违规进程发出警告。当警告发生时,开发人员被告知去跟踪违规的未分配单元。这些工具更详细的使用方法可参照[1][2]。

1.当出现内存泄露时应该做什么

在你进行WINS内存泄露调试之前,同时为了获得更多有用的符号调试信息,将由SDN提供的UIQ 2.1 PDB文件放到你的\epoc32\release\wins\?路径下。对于WINSCW UIQ用户,没必要这样做,因为在他们的调试二进制文件中已经嵌入了那些信息。使用60/80 SDK系列的开发人员可以从SDK  的lib库文件和dll库文件中编译它们[3]。

注意,在以下的讨论中,将会使用Microsoft Developer Studio的术语和例子。CodeWarrior用户在他们的环境和工作流程中能够采用类似的技术。

1.1 跟踪违规的单元

因此,当出现异常并且该异常是内存泄露引起时,你应该设法退出这个应用程序。在那种情况下,系统会提示存在某些单元( cells )没有被删除。通过查询调度栈,找到类似User::__DbgMarkEnd()的函数,就可以找到问题所在。

从调用栈(Call Stack)窗口,点击User::__DbgMarkEnd(RHeap::TDbgHeapType Euser, int 0x00000000)。

在大多数情况下,除非你使用了某些特定的Symbian代码,你将看到的是上述方法的反汇编窗口。这种情况下,将鼠标悬浮在坏单元(badCell)的上方,或者悬停鼠标按Shift+F9,记下提示框中出现的地址。在得到认证的情况下,你将看到上述方法的源代码(在 \e32\?T\ _ KUSR.CPP中)。通过单击鼠标右键(在info.AppendFormat(_L("%x\n"),badCell)上),对坏单元做快速监视。出现在快速监视窗口中的地址就是泄露单元的地址。

另一种获得坏单元地址的方法是在出现警告后让仿真器继续进行调试对话(按F5键),然后记下输出窗口中类似‘Thread panic ALLOC: 16497110’的消息,这里出现的数字实际上就是坏单元地址的十进制表示。

1.2 找到泄露对象的标识

找到泄露对象的标识需要多做一些工作。如果碰巧是从CBase类继承而来的对象,找到泄露对象的标识是非常简单的,因此,完成这项工作最简单的方法就是检查违规对象是否是C类的一个实例。

 1.2.1  如果它是C-类的一个对象

使用“Watch”(监视)或“Quickwatch”(快速监视)函数把它当作 CBase 对象的一个指针。注意,你必须将“0x”写在从剪切板粘贴的十六进制数的前面(如果它以十进制的形式出现,你需要先将它转换成十六进制数)。如果它确实是一个    C 对象,调试器将会告诉你它的类名,在多数情况下,这对解决问题就足够了。如果调试器不能检测到一个虚函数表指针,它将不是一个C对象。此时这种简单的方法就不能用了,我们将不得不尝试其他的技术。

这里有两种方法来缩小对象的搜索范围,一种较快但是粗略,另一种较复杂但是精确。

1.2.2  使用快速数据断点

在可疑部分前面的某个位置设置一个断点,但尽量要靠近可疑代码,这是需要技巧的:-)。停止调试对话,并且重新运行它。一旦运行到刚才设置的断点时,就通过按“Ctrl+b”增加一个数据断点。这时会出现一个属性表单,在数据标记部分,你应该寻找坏单元地址和字节数目的任何变化,而字节数目通常是4(从坏单元的地址开始)。

这个操作告诉调试器通知你那个内存位置的任何变化或访问。按F5继续。如果有代码想要修改该内存单元,调试器应该自动进入该代码。如果你提前在上面设了断点或放置了改变这部分内存的代码,所进入的代码很可能是NewL,

NewLC   或Constr tL方法。

因此,现在你已经发现哪个对象没有被清除或者是存在内存泄漏问题。记住,如果你的搜索没有靠近违规代码,你可能误以为已经被正确释放的对象没有被正确的清空。这是由于这个地址可能被多次重用。

这种情况下,你将不得不耐心地单步运行代码以发现那个地址最后分配的对象,或者尝试下面的技术。

1.2.3 Alloc断点

快速数据断点的另一种方法是在对违规单元进行分配时设置一个断点。所有堆内存通过函RHeap::Alloc(int)来分配。

首先,对违规单元进行分配时设置一个断点。Symbian不提供这个函数的源码,不过你能通过“Edit-Breakpoints- BreakAt”来明确设置断点。

  Symbian 操作系统 C++工程中消除内存泄露 - jianhai1229 - 水木博客

 对于WINS用户,由于有PDB文件,使这个工作变得很容易。使用‘Debug-Go’ (F5)连续执行,直到系统的第一次分配。你将不能看到源码,不过你可以看反汇编代码。滚动反汇编代码,经过retryAllocation标记,直到下一个函数开始前的roundToPageSize那一行。在它前面的RET行上放一个断点。

 搞笑头像 - 阳光小子 - 我的地盘

在那一点,EAX寄存器将包含RHeap::Alloc函数的返回值。如果它与你的违规单元的地址相等,则用Edit-Breakpoints来设置一个断点。首先取消在RHeap::Alloc设置的断点,然后选择你刚设的断点,用Condition来设置条件为:返回值就是你跟踪的单元。

 搞笑头像 - 阳光小子 - 我的地盘

 点击两个对话框中的OK键,然后,用Debug-Go来继续。像前面一样运行应用程序;当在断点处停止执行时,对栈进行检查以寻找违规单元在何处分配。

注意,有时相同的单元可能会多次分配和清除,你只注意最后一次分配就可以。如果单元没有被分配,这可能是由于该次运行与前一次有些不同,泄露的单元在不同的位置;检查在你的应用程序中的虚地址空间,来发现是否是这个原因。

继续执行直到应用程序退出,然后再找下一个违规单元地址。

增设另一个断点,位置与其它断点相同, 使用Condition去查找新的违规单元,使用‘Debug-Restart’来重启调试。你可能会得到报错消息  "Cannot restore all the breakpoints";这时由于在EPOC.EXE第一次执行时EUSER DLL没有马上被加载的缘故。这个问题的一个解决方法是重新激活RHeap::Alloc(int)断点,运行到它被调用,然后恢复其它的条件断点。

 2.积极主动

当然你不必等待坏事情发生,即使只是等到应用程序的退出。积极对待内存泄漏问题,在刚出现问题时就进行捕捉代价会更小。

2.1 积极的使用OOM Loop

一种跟踪内存泄漏和异常处理清理缺陷的技术就是所谓的OOM Loop。这项技术在Symbian操作系统诞生起就开始使用。它是一种可以递增的,确定地得到一块代码内存状态的算法。这种技术是检查由所包括的代码的OOM条件得到的所有可能的异常处理路径。与代码检验 相结合后,这种技术非常强大和令人惊讶,是寻找这类缺陷代价最小的技术。算法所作的是在所测试的代码周围设置堆检验,以确保不存在没有被释放的分配单元。然后,在每次运行中使分配失败,同时不断地增加可以成功分配的数目。因此,在每次运行中增加一个分配并且多检测一个清理路径。

这种技术的简化代码如下:

 for (TInt k=1;;++k) 

__UHEAP_SETFAIL(RHeap::EDeterministic,k); 

__UHEAP_MARK; 

TInt err = TestBlock(); //This internally TRAPs 

__UHEAP_MARKEND; 

User::Heap().Check();// paranoid check for internal heap corruption 

if (err==KErrNone) 

break; 

 当然在TestBlock()中,人们需要捕获(Trap)任何Leave,同时返回使循环能够继续进行或中断的错误码。当涉及到高层分配的情况下,它将看起来很像下面这样(为简便起见省略了方法中的TRAP块):

 for (TInt k=1;;++k) 

RDebug::Print(_L("loop number - %d"),k); 

__UHEAP_SETFAIL(RHeap::EDeterministic,k); 

__UHEAP_MARK; 

TRAPD(err,iModel=CMobInfoAppModel::NewL()) 

if(err==KErrNone) 

{ //last run where everything went well 

delete iModel; 

__UHEAP_MARKEND; 

User::Heap().Check();

if (err==KErrNone) 

break; 

 上面的__UHEAP_SETFAIL宏中的EDeterministic选项是非常重要的。这个选项和循环计数器的增加结合在一起才可以使这项技术检查所有的清理路径。每当加上一个OOM,栈内的所有对象都要被清空,所有TCleanupItems将需要被调用来清空所有资源。如果异常处理逻辑存在问题或者一个资源在一次运行后仍然存在,它将会在检验点被标记。这个检验点通过标记检验(__UHEAP_MARKEND)和实际检验的结尾来设置,在实际检验的结尾处存在和运行前一样多的已分配单元。

 OOM 循环可以被作为一项独立的技术使用,也可以在开发和测试过程中嵌入到代码中。在这种情况下,考虑到完整性,将像以下这样:

 //be defensive since PushL may leave, thus pre-alloc enough for the test 

for (TInt j=0;j<1000;++j) 

CleanupStack::PushL(&j);

CleanupStack::Pop(1000); 

//Extra paranoia marks in case the framework has a memory leak  

__UHEAP_MARK; 

for (TInt k=1;;++k) 

RDebug::Print(_L("loop number - %d"),k); 

__UHEAP_SETFAIL(RHeap::EDeterministic,k); 

__UHEAP_MARK; 

TRAPD(err,iModel=CMobInfoAppModel::NewL()) 

if(err==KErrNone) 

delete iModel; } 

__UHEAP_MARKEND; 

User::Heap().Check();

if (err==KErrNone) 

break; 

__UHEAP_MARKEND; //end of paranoia checks  

__UHEAP_RESET; //reset failure tool 

User::Heap().Check();

 2.2 在什么地方加入 OOM Loop?

OOM Loop测试实际上可以放到任何地方,但是如果它能包裹的代码越多,它将越有用。例如在UIQ框架中,它能够包裹模型(‘model’)和AppUi的创建。因此,在派生(从CQikDocument类)的文档类中,它能够包裹iModel和CEikAppUI的创建,像下面的例子一样(取自Mobinfo API t测试程序)。

 void CMobInfoAppDocument::Constr tL() 

__UHEAP_MARK; 

for (TInt k=1;;++k) 

RDebug::Print(_L("loop number - %d"),k); 

__UHEAP_SETFAIL(RHeap::EDeterministic,k); 

__UHEAP_MARK; 

TRAPD(err,iModel=CMobInfoAppModel::NewL()) 

if(err==KErrNone) 

 { 

delete iModel; 

__UHEAP_MARKEND; 

User::Heap().Check();

if (err==KErrNone) 

break; 

__UHEAP_MARKEND; 

__UHEAP_RESET; 

User::Heap().Check();

iModel = CMobInfoAppModel::NewL(); 

ResetModel(); 

 注意:人们可能想按照AppDocument的CreateAppUiL()中描述的方法去做。这实际上只对应用程序框架的创建者有用,因为它会检验AppUi构造函数以及后面内容的路径。开发者需要注意当建立一个应用时这样做是受限的,因为它们的代码实际上是在AppUi的Constr tL()中的第二级构造阶段被实例化。

Symbian操作系统的视图交换结构采用了两层视图构建(有些产品中甚至是三层的),因此它能够使应用程序(和视图)的启动时间最小化,同时还能最小化从未使用视图分配的内存位置开始的所使用的内存。所以,用OOM Loop对于那些建立在视图的Constr tL()和ViewConstr tL()中的块是有益处的。这样做的一个较小的副作用是,为了使代码令人信服,在新的私有方法中人们需要对这些块进行重构。

如果你在开发的过程中,按上面所说的进行操作,每次程序框架开始你的应用程序的时候同时它也激活视图,OOM Loop甚至可以在你关闭应用前就可以迅速发现问题。

记住:尽管总可以在应用程序退出前找到内存泄露的缺陷,你代码的异常安全性将不会加强除非像在OOM Loop中那样处理。

2.3  商业工具

像上面所说,Symbian操作系统C++框架具有对多种跟踪以及主动地确保资源不泄漏的支持。嵌入的‘堆和文件故障工具(Heap and File Failure Tool)’是这类支持的表现。为建立调试,这个工具被嵌入到所有Symbian操作系统平台的(Uikon)应用程序框架中。

 搞笑头像 - 阳光小子 - 我的地盘

使用Uikon调试组合键Ctrl+Alt+Shift+P,这个工具就会出现,允许人们为文件服务器资源和内存分配设置确定的或随机的故障点。注意,从工具来说,这样的处理只应用在它被调用的环境中。

就OOM测试而言,这里有两种主要的方式在所有预先确定数目的分配中使用这个工具。一种是将这个工具随机地放在失败的内存分配中,另一种是将该工具放在一个确定的位置。使用这个工具的第三种方式是在调试模式下,运行应用程序的时候把它当作一种捷径在任何点强制内存分配出错。通过将应用堆故障类型(App heap failure type)设为确定(deterministic)同时将故障率(failure rate)设为1。这个捷径方便地用于测试当应用程序运行时如果该处出错会有什么发生。

在这样做时,你可以用另外一个调试组合键Ctrl+Alt+Shift+A在屏幕上显示每个测试点单元分配的数目。因此,如果在一个强制的OOM失败前面和后面使用上面的组合键,不在代码中嵌入任何宏的条件下,人们可以马上确认在OOM条件下,功能的某个部分是否能够正确运行。

2.4  结束语

使用上面获得的技术,你应该可以很好地编写没有内存泄漏的代码。

参考文献:

[1] ‘Symbian OS C++ for Mobile Phones’, Richard Harrison, 2003, Wiley

[2] ‘Symbian OS Explained’, Jo Stichbury, 2004, Wiley

[3] ‘Generating debug symbols for the emulator’, Mika Raento,

http://www.cs.helsinki.fi/u/mraento/symbian/symbols.html

支持开发者库

想要及时得到Symbian开发者网络上最新制作的文档通知么?

订阅Symbian社区时事通讯

Symbian社区时事通讯每一个月都将带给你关于Symbian操作系统的最新消息和资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值