Download presentation
Presentation is loading. Please wait.
1
异常退出和清除栈
2
简介 Symbian OS 被设计为可以在只有有限内存的设备上良好运行的系统, 它使用清除栈来保证在错误发生时不会发生内存泄漏。
Symbian OS 的两个最基本的编程模式是异常退出(Leave)—— Symbian OS 上轻量级的异常——和清除栈(Cleanup Stack),它是用 来管理发生异常退出事件时的内存资源和其他资源的。
3
异常退出和清除栈 异常退出: Symbian OS 中轻量级的异常
了解在Symbian OS 在v9之前,并不支持标准C++ 中的异常机制 (try/catch/throw), 而是使用一个轻量级的替代品: TRAP 和异常退出 ,这个机制在 Symbian OS v9 中仍然是优先使用的 了解异常退出是 Symbian 错误处理机制中一个很基础的部分,并在整个系统中都使用。 理解异常退出和C语言中 setjmp/longjmp 声明的相似性 认识可以产生异常退出的典型系统函数,包括:User::LeaveXXX()函数和 new(ELeave) 能够列出可以产生一个异常退出的典型场景(比如堆分配时内存不足) 理解 new(ELeave) 机制能保证在不发生异常退出时返回的指针总是正确有效的
4
为什么不使用标准C++的异常? 最初设计Symbian OS的时候,异常还不是C++标准的一部分 强调紧凑的操作系统和客户端代码
人们发现无论是否有异常被抛出,异常处理支持都会大大增加编译后代码的规模,并带来了运行时存储 器的开销。 强调紧凑的操作系统和客户端代码 异常对于 Symbian OS 来说意味着太多的开销 因此,提供了一种简单、轻量级的标准C++中异常机制的替代方法——异常退出(Leave) 异常退出可用来将错误传播它能够被处理的地方 There is some confusion over whether C++ exceptions existed when C++ was designed. They did, but the standard was not formalised.
5
为什么不使用标准C++的异常? Symbian OS v9.x
在 Symbian OS v9.x 以前的版本中, 编译器被明确设定为禁用 C++ 异常处理。 在Symbian OS v9.x 中, 得益于编译器进一步发展, 已经可以支持标准 C++ 异常处理并提供了一个 更加开放的环境。 这就使得将现有 C++ 代码移植到 Symbian 平台变得更容易了。
6
什么是异常退出? 异常退出会终止正在执行的代码 User::Leave() 或 User::LeaveIfError() TRAP宏
在异常退出发生的地方,并在异常退出事件被捕获的地方恢复代码执行 Symbian OS 里面的TRAP Harness(陷阱套)是一个TRAP宏定义 Leave将栈指针设置成 TRAP 的上下文处,然后跳转到该位置并恢复寄存器的值。 Leave并不会终止线程的执行流程 User::Leave() 或 User::LeaveIfError() 近似于 C++ 中的 throw指令 除了不调用栈中对象的析构函数 TRAP宏 可以看作 try 和 catch 组合 —— 它们将会在后面章节详细讨论
7
setjmp() 和longjmp() 方法
TRAP 宏和 User::Leave() 类似于标准库的 setjmp() 和longjmp() 方 法 调用 setjmp() 将把要跳转到的位置信息保存在跳转缓冲区中 ... 被longjmp()用来决定要执行跳转的目标位置 This slide roughly shows the workings and the relationship between a TRAP and Leave. It is important to note loosely speaking anything after the TRAP and before the leave is not ‘recorded’ i.e. if something is allocated in SomeLeavingFunctionL it will not be dellocated if a leave occurs. This is a precursor to the cleanup stack. void SomeFunction() { // do something ... TInt result; TRAP(result,SomeLeavingFunctionL(...)); } 1. setjmp 例如保存的栈位置 2. 常规 C++ 函数调用 void SomeLeavingFunctionL (...) { ... User::Leave(KErrNotFound); } 4. 恢复执行, result = KErrNotFound 3. 发生longjmp,回退到 TRAP
8
异常退出行为 异常退出机制仅回收栈中的对象 这就是为什么T 类和内建类型可以安全地在栈上实例化并使用
和C++的异常不同, C++异常机制会调用栈对象的析构函数 如果一个栈对象拥有一个资源,而其资源又必须在析构函数进行回收或释放,那么在发生异常 退出时就会产生资源的泄露 这就是为什么T 类和内建类型可以安全地在栈上实例化并使用 T 类被限制成只能拥有内建类型或其他T类的所有权,它不能占有资源,所以也就不需要析构函数 如果发生了异常退出,一个基于栈的 T 类对象将被正确的清除,因为实际上当栈被清空时本来就没有什 么事情可做 R 类也可以在栈上分配 ——但是它们必须是“异常退出安全”的 清除栈就是用在这些地方,后面还将简单讨论
9
什么导致了异常退出? 典型的异常退出函数 函数在以下情况也可能发生异常退出:
如果一个操作不能保证一定能成功执行的话就要使用异常退出机制,比如下面的情况: 分配内存空间——内存不足时导致失败 创建文件——磁盘空间不够导致失败 函数在以下情况也可能发生异常退出: 调用了另一个可能异常退出的函数,并且发生了异常退出 显式调用一个会产生异常退出的系统函数,如 User::Leave() 使用了以 ELeave 为参数的 new 操作符的重载形式
10
为什么要使用异常退出? C++异常机制在使用时花销太大了,为什么不仅仅是检查一下呢?举 例说: 对开发者依赖太大——很容易遗忘检查
CCat* InitializeCat() { CCat* cat = new CCat(); if (cat) cat->Initialize(); return (cat); } else return (NULL); If the students already know the rationale behind C++ exceptions, the same reasons apply to leaves. 对开发者依赖太大——很容易遗忘检查 即使存在低内存测试,未检查的代码也很容易带来麻烦 产生不必要的复杂代码
11
使用 new(ELeave)分配堆内存 Symbian OS 重载了全局的 new操作符以支持异常退出
如果没有足够的堆内存进行分配,可以选择异常退出 使用重载操作符时,可以不经进一步检查而直接使用成功分配内存后返回的指针(没有分配成功时将 发生异常退出) 为开发者省去了需要额外编写的检查代码 下面的代码片断中展示了 Symbian OS 中的new操作符重载的使用 CCat* InitializeCatL() { CCat* cat = new(ELeave) CCat(); cat->Initialize(); return (cat); }
12
User 类中可引起异常退出的函数 User::LeaveIfError() User::Leave()
尝试传入一个整型参数值,若该值小于零,则产生一个异常退出 使用整数值作为异常退出码,比如:在 e32std.h 中所定义的某个 KErrXXX 错误常 量 User::LeaveIfError() 可用来将一个返回标准 Symbian OS 错误码的无异常退出 函数转化成一个以该值为异常退出码的异常退出函数。 例子:User::LeaveIfError(FunctionReturningAnError()) User::Leave() 不做任何参数值的检查,只是简单地以传入的整数值做为异常退出码来进行异常退出
13
User 类中可引起异常退出的函数 User::LeaveNoMemory() User::LeaveIfNull() 不带参数
异常退出码被硬编码成 KErrNoMemory ,实际的效果等价于调用 User::Leave(KErrNoMemory) User::LeaveIfNull() 带有一个指针值为参数,如果该指针指为 NULL ,就以 KErrNoMemory 为异常退出码 发生异常退出 某些时候,这个函数可用来包裹那些分配内存成功则返回指针值、失败则返回NULL的 无异常退出函数
14
异常退出和清除栈 如何使用异常退出机制 包含可能会发生异常退出代码的函数使用 L 作为名称后缀来标示 (比如, InitializeL())
能够识别哪些函数是不能够实现异常退出安全的,而哪些能够 理解异常退出是用于错误处理的;代码应该尽可能少地返回错误值并且能够异常退 出机制 理解为什么异常退出不能在构造函数和析构函数中出现
15
如何使用异常退出 不安全异常退出的问题 假定一个函数已在堆上分配空间,并且这块空间只被一个局部指针变量标示。
如果异常退出发生在函数内部,指针被异常退出函数销毁(因为栈结构被清除回归到 TRAP 处理者),而由该指针所指向的堆空间无法再回收使用,这就引发了内存泄露。
16
一个内存泄漏的反例 一个不安全异常退出的例子 ... ... void SomeFunction() {
// do something ... TInt result; TRAP (result,UnsafeFunctionL()); } 1. setjmp 比如保存的栈位置 The code is unsafe because the memory allocated on the heap in the call to CTestClass::NewL() will become inaccessible if the subsequent call to FunctionMayLeaveL() does leave. This means that test can never be deallocated (it is said to be “orphaned”) and will result in a memory leak. The function is not “leave-safe”. 5. 在TRAP处恢复执行, 可是由test指向的堆内存不可访问。在本例中, CTestClass 在标记3处分配内存但是在标记4处发生了异常退出,所以 delete test 操作永远不会被执行,这样 test 就被“遗弃”了。 2. 标准 C++ 函数调用 void UnsafeFunctionL() { CTestClass* test = CTestClass::NewL(); test->FunctionMayLeaveL(); delete test; } 3. 在堆上分配内存 4. 发生异常退出 (longjmp) *** 总不删除 ***
17
介绍一下清除栈 让函数成为异常退出安全的 只有由局部变量指向的堆对象必须在调用任何可能发生异常退出的函数之前被推入到 清除栈中
当异常退出发生时,清除栈将删除其对应的堆内存空间 关于清除栈的知识将会在后面详细的讲解
18
什么地方使用清除栈 不要担心确切的细节——它们将在后面涉及到 void SomeFunction() {
// do something ... TInt result; TRAP (result,UnsafeFunctionL()); } 1. setjmp i比如. 保存的栈位置 6. TRAP 宏可以检测到推入清除栈上的对象并在异常退出过程中将它销毁,然后继续运行程序 void UnsafeFunctionL() { CTestClass* test = CTestClass::NewL(); CleanupStack::PushL(test); test->FunctionMayLeaveL(); CleanupStack::Pop(test); delete test; } 2. 标准 C++ 函数调用 3. 在堆上分配内存空间 4. test (已分配内存地址) 被放在清除栈上 5. 发生异常退出 (longjmp) 如果没发生异常退出,将该项弹出清除栈并正常销毁是很重要的 不要担心确切的细节——它们将在后面涉及到
19
更多Symbian OS 命名规范 如何才能知道一个函数是否会发生异常退出? Symbian OS 有一个命名规范指明了这一点
如果一个函数可能会异常退出,它的名字结尾必须以“L”为后缀 这点非常重要:如果一个异常退出函数没有按照规则进行命名,函数的调用者可能就不会防范异常退出的情况,以致造成内存泄漏。Symbian OS 提供了一个工具,LeaveScan, 来检查没有正确命名的异常退出函数
20
更多的 Symbian OS 规范! 一般的 发生的任何错误 异常退出函数应返回 void 值
除非它们使用由函数分配资源的指针或引用作为返回值 根据定义,函数在异常退出时要有一个错误代码 —— “异常退出码” ——所以它们不需要再返回一 个错误代码 发生的任何错误 在异常退出函数中都会被作为异常退出而传递出来 如果函数没有发生异常退出,即可以认为它被成功调用,并将正常返回
21
典型的异常退出的例子 四个可能发生的异常退出: 1.使用重载 new 操作符来分配内存 2.将一些对象压入清除栈顶端 (后面会讲)
TInt UseCat(CCat* aCat); // Forward declaration CCat* InitializeCatL() { CCat* cat = new(ELeave) CCat(); // (1) CleanupStack::PushL(cat); // (2) cat->InitializeL(); // (3) User::LeaveIfError(UseCat(cat)); // (4) CleanupStack::Pop(cat); return (cat); } 1.使用重载 new 操作符来分配内存 2.将一些对象压入清除栈顶端 (后面会讲) 3.调用一个可能异常退出方法 4.调用会返回错误码的非异常退出函数,该函数被一个会产生异常退出的系统函数所包裹
22
何时不能发生异常退出: 构造函数 & 析构函数
何时不能发生异常退出: 构造函数 & 析构函数 如果在构造函数中发生异常退出: 将对象处于不确定的状态 如果构造函数失败,即因为缺乏足够的资源而无法创建或初始化对象,就可能会造成内存泄漏 必须使用两阶段构造法来避免这种情况的发生(下一讲讨论) 在析构函数和清除代码中一定不能出现异常退出 在析构函数中途发生的异常退出将导致对象的不完全析构—— 这将造成资源的泄漏 一个析构函数本身会作为一个异常退出的清除过程的一部分被调用,这样在清除过程中再次发生异 常退出就不合适了... ...因为这样至少会掩盖了最初发生异常退出的原因 Two-phase construction is covered in a later lecture
23
成员变量是异常退出安全的 堆变量 成员变量 当异常退出发生时,只是由局部变量引用的堆变量可能会被遗弃(orphaned)
不会遭遇类似的命运——除非在以后某处调用它们的析构函数时,析构函数忘了将它们清除 void CTestClass::SafeFunctionL() { iMember = CCatClass::NewL(); // 在堆上分配成员变量 FunctionMayLeaveL(); // 对iMember是安全的 }
24
异常退出和清除栈 比较异常退出和致命错误(Panics) 理解异常退出和致命错误的区别
明白致命错误由断言(Assertion)失败产生,断言是用于在开发过程中标记编程错 误的 明白一个异常退出不应用来指引正常的程序逻辑
25
比较异常退出和系统错误 异常退出是用来体面地处理问题 异常退出发生在内存不足或磁盘空间不足等异常情况下,是用来替换错误代码返回
异常退出应该仅仅用于将代码中的错误或异常传播到可以被体面地处理的地方 不应用来指引程序逻辑的正常流程 异常退出应该总是被捕获和处理——它们并不终止执行流程
26
比较异常退出和系统错误 致命错误——一种 ‘停止全部工作’的编程错误处理机制 致命错误只能用于: Symbian OS 致命错误
致命错误不能被捕获和处理 一个致命错误会终止它所属的线程 —— 通常是整个应用程序 致命错误只能用于: 在断言声明中检查代码逻辑并在开发过程中更正编程错误(糟糕的用户体验) 在开发中,如果系统或应用程序代码发生了致命错误,有必要去找出原因并进行修正 Symbian OS 致命错误 在Symbian Developer Library中有文档说明 致命错误和断言会在后面的课程中进行更深入的讲解
27
异常退出和清除栈 什么是 TRAP? 认识 TRAP 处理者的特征 理解因为效率的原因,对 TRAP 的使用应保持在最少
理解在Symbian OS 中以 函数名C 和 D 结尾的函数的意思
28
什么是 TRAP ? Symbian OS 提供了两种陷阱套宏 TRAP 和 TRAPD :
两者都用来捕获异常,使得它们被处理 TRAPD 声明了一个用于保存异常退出错误码的变量 TRAP 必须先声明一个 TInt 变量 TRAPD(result, MayLeaveL()); if (KErrNone!=result) ... 等价于: TInt result; TRAP(result, MayLeaveL()); 如果 MayLeaveL() 函数发生了异常退出,而这个函数又是在陷进套中执行,于是程序控制权将立刻返 回到 TRAP 陷阱套宏 变量result会保存与异常退出对应的错误代码,而如果异常退出没有发生,这个值将是 KErrNone
29
Traps 产生的开销 每一个 TRAP 这些因素
对执行规模和执行速度有影响 进入和退出一个 TRAP 宏将导致内核执行者调用 TTrap::Trap() 和 TTrap::UnTrap() 为了访问内核资源,内核调用将用户端代码切换为处理器特权模式 就执行速度来说,内核调用是很昂贵的 另外,在运行期间需要分配一个结构体用来保存当前线程的栈内容,这样在异常退出发生时就可以返 回到保存的状态 这些因素 加上由 TRAP 宏产生的内联代码,总计成为一笔非常可观的开销 TRAPS 的数量应尽可能减少 聪明的使用, 与同行进行代码审查,必要时进行重构(refactor) The primer contains a good example for refactoring (p90-91)
30
以 D 为后缀的命名规范的说明 在 UI 编程中 以 D 为后缀所表示的意思完全不同于这里!
因为函数会在结束对某对象使用时会删除(delete)对象,任何调用代码不应该再试图 做这样的工作 这类函数很好的一个例子是 CEikDialog::ExecuteLD()
31
异常退出和清除栈 清除栈 知道如何使用清除栈使代码成为异常退出安全的, 这样在异常退出事件中不会发生内存泄漏
理解即使CleanupStack::PushL()发生了异常退出, 也不会出现内存泄漏 知道将项目从清除栈中移出的顺序,以及如何使用 CleanupStack::PopAndDestroy()和 CleanupStack::Pop() 识别对清除栈的正确和错误的使用 理解将一个并非继承自 CBase 的 C 类放置到清除栈上的后果 知道如何对 C, R, M 和 T 类对象使用 CleanupStack::PushL() 和 CleanupXXXPushL() 方法,如何对C++ 数组使用 CleanupArrayDeletePushL() 方法 理解在 Symbian OS 中以 C 和 D 作后缀的函数的意义
32
TRAPS, 异常退出和清除栈 概述 这将保证 当堆对象只能通过指向要发生异常退出函数的局部指针来访问的时候,就会发生内存泄漏
在调用可能会异常退出的代码之前,应该把指向并非是异常退出安全的对象的指针先置于清 除栈中 这将保证 当异常退出发生时,作为 TRAP 的一部分,被指向的对象将被正确销毁,清除栈将负责回 收所有置于其上的对象
33
概述: 使用中的清除栈 void SomeFunction() { // do something ... TInt result;
概述: 使用中的清除栈 void SomeFunction() { // do something ... TInt result; TRAP (result,UnsafeFunctionL()); } 1. setjmp 如: 保存的栈位置 6. TRAP 检测到某个项目被推入清除栈中, 并作为异常退出处理的一部分来销毁它. 然后程序继续执行. void UnsafeFunctionL() { CTestClass* test = CTestClass::NewL(); CleanupStack::PushL(test); test->FunctionMayLeaveL(); CleanupStack::Pop(test); delete test; } 正常 C++ 函数调用 3. 在堆上分配内存 4. test (分配的内存地址) 被置于清除栈上 5. 发生异常退出 (longjmp) 如果没有发生异常退出, 一定要将该项目弹出清除栈并正常销毁它
34
CleanupStack 类 所有成员是静态的 在 e32base 中定义 class CleanupStack { public:
IMPORT_C static void PushL(TAny* aPtr); IMPORT_C static void PushL(CBase* aPtr); IMPORT_C static void PushL(TCleanupItem anItem); IMPORT_C static void Pop(); IMPORT_C static void Pop(TInt aCount); IMPORT_C static void PopAndDestroy(); IMPORT_C static void PopAndDestroy(TInt aCount); IMPORT_C static void Check(TAny* aExpectedItem); inline static void Pop(TAny* aExpectedItem); inline static void Pop(TInt aCount,TAny* aLastExpectedItem); inline static void PopAndDestroy(TAny* aExpectedItem); inline static void PopAndDestroy(TInt aCount,TAny* aLastExpectedItem); };
35
CleanupStack 类 是一个栈 ——后进先出 (“LIFO”) Pop() 和 PopAndDestroy()
必须按严格的顺序将指针推入和弹出清除栈 Pop()调用必须与 PushL()调用的顺序相反 (LIFO) Pop() 和 PopAndDestroy() 将栈上最后一项弹出栈 将栈上最后一项弹出并将其销毁 Pop(TInt aCount)和 PopAndDestroy(TInt aCount) 将最后aCount 个项目弹出 将最后aCount个项目弹出并予以销毁
36
CleanupStack 类 Pop(TAny* aExpectedItem)
Pop(TInt aCount, TAny* aLastExpectedItem) PopAndDestroy(TAny* aExpectedItem) PopAndDestroy(TInt aCount,TAny* aLastExpectedItem) 和以前将单个或多个项目弹出的方法一样 最后的项目被命名为: aExpectedItem 在调试构建(debug build)中, 当被弹出的项目和传入的项目不一样时清除栈会发生致命错 误 使用这些方法是良好的习惯 Check(TAny* aExpectedItem) 检查栈顶的项目是不是期望的那个,但是并不弹出它 如果不是将发生致命错误 作为调试工具使用
37
PushL() 为什么 CleanupStack::PushL()会异常退出? 如果没有可用的剩余槽位
它可能需要为存储指针分配内存,因此可能由于内存容量低而失败 传入 PushL()方法的的对象都将是安全的 传入PushL()的对象不会被遗弃,因为当创建清除栈时它至少就有一个多余的槽位(slot) PushL() 将指针添加到下一个空闲的槽位,并检查是否有空闲的槽位供下次 PushL()调用使 用 如果没有可用的剩余槽位 清除栈实现会试图为将来的使用分配更多的内存槽 如果分配失败了,就会产生异常退出 传入的指针已经被安全保存,所以它指向的对象会被安全的清除
38
PushL() PushL() 方法有三种重载形式用来将一个项目放在清除栈上:
每一种重载形式决定了该项目在后来在发生异常退出或是调用 CleanupStack::PopAndDestroy()而要被清除时,将被如何销毁 IMPORT_C static void PushL(CBase* aPtr); IMPORT_C static void PushL(TAny* aPtr);IMPORT_C static void PushL(TCleanupItem anItem);
39
PushL() 和 PopAndDestroy()
IMPORT_C static void PushL(CBase* aPtr) 接收一个派生自 CBase 的对象指针 它被设置为通过调用delete 操作来进行销毁 CBase 派生对象的虚析构函数会被调用 这就是为什么 CBase 类拥有一个虚析构函数而且必须作为所有C类的基类使用的原因 。这也 意味着C类对象可以被置于清除栈中,并在异常退出发生时能够被安全地销毁
40
PushL() 和 PopAndDestroy()
IMPORT_C static void PushL(TAny* aPtr) 用于将任何不是继承自 CBase 的堆对象压入清除栈 已经被分配在堆空间的T 类对象和结构体 对象占用的堆内存通过调用 User::Free()来释放 不调用 delete 操作—— 所以析构函数不会被调用 如前所述,T 类没有析构函数
41
PushL() 和 PopAndDestroy()
IMPORT_C static void PushL(TCleanupItem anItem) 用来使具有清除处理机制的对象是异常退出安全的,这类对象不使用 CBase类的删除 操作或者简单的内存回收来进行清除 比如 R 类或 M 类, 或是具有定制清除例程的类 TCleanupItem 对象封装了一个指向对象的指针和一个指向清除该对象的函数指针 清除函数可以是局部函数或是类的静态方法 异常退出或者调用PopAndDestroy()将对象从清除栈中移除,并调用由 TCleanupItem 提供的清除函数 Symbian OS 还提供了一系列的模版工具函数来产生一个 TCleanupItem 类型的对 象,并将其推入到清除栈中
42
CleanupXXXPushL()模板函数
CleanupReleasePushL() 该清除方法调用对象的 Release() 方法 典型的用于将一个 M 类指针引用的对象变成是异常退出安全的 CleanupDeletePushL() 对传入函数的指针执行 delete 操作 典型的用于 M 类对象,它们不能用重载的 CleanupStack::PushL(TAny*) 方 法推入到清除栈中 由 M 类指针指向的对象通常不能简单的通过调用 User::Free() 来回收
43
CleanupXXXPushL()模板函数
CleanupClosePushL() 这个清除方法调用对象的 Close()函数 典型的用于使将基于栈的 R 类对象成为异常退出安全的 The emphasis in the example of the filesystem RFs being a resource connect and close is required RFs theFs; User::LeaveIfError(theFs.Connect()); CleanupClosePushL(theFs); ... //调用可能会异常退出的方法 CleanupStack::PopAndDestroy(&theFs);
44
CleanupXXXPushL()模板方法
CleanupArrayDeletePushL() 用于将由 T 类(或内建类型)的对象组成的基于堆的 C++ 数组推入到清除栈中 调用 PopAndDestroy( ) 的时候,为数组分配的内存用 delete[ ] 来清除 数组元素不调用析构函数 The emphasis in the example of the filesystem RFs being a resource connect and close is required
45
何时从清除栈上移除一个项目 所有权转换 一个对象决不能被清除超过一次
如果一个指向清除栈中对象的指针后来又被存到了另外的地方,比如作为另一个对象的成员变量, 而这个对象在异常退出后仍可被访问,那么这个指针应从清除栈上被弹出 void TransferOwnershipExampleL { CItem* ptr = new(ELeave) CItem(); CleanupStack::PushL(ptr); iItemPtrArray->AppendL(ptr); CleanupStack::Pop(ptr); } itemPtrArray is a pseudo dynamic array for this example, proper Symbian dynamic arrays are discussed later in the course. 1. 指向一片内存空间的栈指针 ptr 在堆上分配空间 2. 下一个函数可能会异常退出,所以将指针放在清除栈上就意味着如果 AppendL() 异常退出了,堆内存将被释放 3. iItemPtrArray 并未复制 CItem 对象,而是通过分配一个新的槽位来存储指针并取得了它的持有权,如: CItem 对象的地址,槽位i的分配可能会失败因此它是一个异常退出函数,而ptr指针提前就被放在清除栈上面了 4. iItemPtrArray 现在拥有由 ptr 指向的堆对象了,所以 ptr 可以安全的从栈中弹出 (在 iItemPtrArray 拥有它的时候不能销毁它,否则它会失效)
46
指针成员变量 类成员变量指针 不能被推入到清除栈中 这种对象可以通过持有它们的对象来访问,也由持有它们的对象在适当的时候销毁
典型的是在其析构函数中销毁 所以不需要利用清除栈机制使类成员变量指针是异常退出安全的 下一页将详细讲述这类错误的后果...
47
编码错误举例 class CSimple : CBase { public: ~CSimple(); void MayLeaveFuncL(); private: PrivateMayLeaveL(); CItem* iItem; }; CSimple* simple = new (ELeave) CSimple(); CleanupStack::PushL(simple); 1. 由于下一个函数可能会异常退出,CSimple 类对象被创建后就被推入到清除栈中 2. 对 simple 调用一个异常退出方法 TRAPD(res,simple->MayLeaveFuncL()); ... void CSimple::MayLeaveFuncL() { iItem = new (ELeave) CItem(); CleanupStack::PushL(iItem); PrivateMayLeaveL(); CleanupStack::Pop(iItem); } Important not to get stuck in a debate with this (the devil is in the detail) 3. 将成员变量推入到清除栈 (哇!) 5 TRAP 解决了这个问题,清空了清除栈 如: CSimple::iItem 被删除 4. 当异常退出发生时怎么办? ... 6. 代码逻辑上就是由弹出并删除 simple 对象组成的 CleanupStack::PopAndDestroy(simple); CSimple::~CSimple { delete iItem; } 7. 致命错误! 但是这会调用 CSimple 的析构函数, 析构函数就将删除已经被 TRAP 删除的 iItem
48
何时在清除栈上遗留指针 (更多命名规范) 如果有对象指针被压入到清除栈,直至函数返回时仍保留在清除栈上,则该函数应以 C 为函数名后缀
这就告诉调用者,如果函数成功返回了,清除栈上仍然有多余的对象指针 /*static*/ CSiamese* CSiamese::NewLC(TPointColor aPointColour) { CSiamese* me = new(ELeave) CSiamese(aPointColour); CleanupStack::PushL(me); // Make this leave-safe... me->ConstructL(); return (me); // me remains on the cleanup stack } 这类函数很有用,因为调用者可以实例化 CSiamese 类对象,并立即调用一个异常退出函数, 而其间无需将分配好的对象再压入清除栈了
49
何时在清除栈上遗留指针 遗留对象在清除栈中的函数 这是因为清除栈以嵌套层次的方式保存对象 不能在TRAP 装置直接调用
下面的代码将在其返回 TRAPD 宏时发生 E32USER-CBASE 71 系统错误 CSiamese* MakeSiamese(TPointColor aPointColour) {// The next line will cause a panic (E32User-CBase 71) CSiamese* pCat = TRAPD(r, CSiamese::NewLC(aPointColour)); return (pCat); }
50
创建清除栈 清除栈按如下方法创建: CTrapCleanup* theCleanupStack = CTrapCleanup::New();
delete theCleanupStack; 一旦清除栈被建立后,任何要使用它的leaving代码必须在底层次的TRAP装置中调用 没必要为GUI 应用程序创建清除栈,因为应用程序框架本身会创建。 出现以下情况,必须创建一个清除栈: 编写一个服务器 一个简单的控制台测试应用程序 任何创建了要使用清除栈的额外线程的代码(或者要使用了清除栈的代码)
51
异常退出和清除栈 发现内存泄漏 明白使用 __UHEAP_MARK 和 __UHEAP_MARKEND 宏来发现内存泄漏
52
检测内存泄漏 在 Symbian OS 里内存是有限的资源 必须被小心谨慎的管理以保证不会因为内存泄漏而浪费
应用程序必须体面的处理当内存资源耗尽所产生的任何异常情况 Symbian OS 提供了一套仅供调试时使用的宏,可以直接添加到代码中来检查是否发 生了内存泄漏 有很多宏可供使用,但是最普遍使用的宏如下定义: #define __UHEAP_MARK User::__DbgMarkStart(RHeap::EUser) #define __UHEAP_MARKEND User::__DbgMarkEnd(RHeap::EUser,0)
53
检测内存泄漏 __UHEAP_MARK 和__UHEAP_MARKEND宏 引发致命错误 验证缺省的用户堆保持一致性
在调试构建时指示堆未保持一致性 产生的致命错误是 ALLOC nnnnnnnn nnnnnnnn 是指向第一个被遗弃的堆单元的十六进制指针 堆检查的宏可以彼此嵌套,并可以在代码的任意地方使用 它们在 Symbian OS 的发行构建中会被忽略掉,所以可以把它们留在成品代码中而不会对代码 的规模和速度有任何影响
54
异常退出和清除栈 异常退出: Symbian OS 的轻量级异常 如何使用异常退出 比较异常退出和系统错误 什么是 TRAP? 清除栈
检测内存泄漏
Similar presentations