Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 57
Текст из файла (страница 57)
To test theleave safety of the method, it is necessary to check that failure of each ofthese allocations is handled correctly. The best way to do this is to callSomethingL() inside a loop as follows:1.Set the __UHEAP_FAILNEXT parameter.2.Use __UHEAP_MARK to mark the heap.3.Call CTest::SomethingL() inside a TRAP macro.4.Call __UHEAP_MARKEND to check that no memory has been leaked.5.Check the error from the TRAP; if it’s KErrNoMemory, you shouldgo round the loop again, incrementing the number of allocationswhich succeed before the failure point. If it’s KErrNone, all theallocation points inside SomethingL() have been tested and thetest is complete.void TestLeaveSafetyL(){// extra check to demonstrate that these macros can be nested__UHEAP_MARK;CTest* test = CTest::NewLC();for (TInt index=1;;index++){__UHEAP_FAILNEXT(index);__UHEAP_MARK;// This is the function we are OOM testing1The call to User::__DbgMarkEnd() will raise a panic if the heap is inconsistent.The panic raised is ALLOC nnnnnnnn, where nnnnnnnn is a hexadecimal pointer to thefirst orphaned heap cell.
I’ll discuss how to track down what is causing a panic of this typeshortly.HEAP-CHECKING MACROS267TRAPD(r, test->SomethingL());__UHEAP_MARKEND; // Panics if the heap memory is leaked// Stop the loop if r! =KErrNoMemoryif (KErrNoMemory!=r){User::LeaveIfError(r);break; // For cases where r==KErrNone}}CleanupStack::PopAndDestroy(test);__UHEAP_MARKEND;__UHEAP_RESET;}If your code fails to propagate allocation failures, then the loop abovewill not work, although your code will appear to be leave-safe.
Supposethat CTest::SomethingL() makes five heap allocations, but whenthe third allocation fails, the error is ”swallowed” and not propagatedback to the caller of SomethingL() as a leave. The loop will terminateat the third allocation and the test will appear to have been successful.However, if a leave from the fourth allocation causes a memory leak,this will go undetected. It is thus important to make sure that your codealways propagates any heap allocation failures.Another problem is that it is possible that some code will allocatememory which is not freed before the __UHEAP_MARKEND macro.
Agood example is the cleanup stack, which may legitimately be expandedas code runs but does not free the memory allocated to it until thecode terminates (as I described in Chapter 3). This problem can alsooccur when using any dynamic container class that supports automaticbuffer expansion to hide memory management (Chapter 7 examines theseclasses in detail).To prevent a false memory leak panic in your test code, it is advisableto expand the cleanup stack beyond what it is likely to need before thefirst __UHEAP_MARK macro:void PushLotsL(){// Expand the cleanup stack for 500 pointers{TInt* dummy = NULL;for (TInt index =0; index<500; index++)CleanupStack::PushL(dummy);}CleanupStack::Pop(500);}The __UHEAP_X (where X = MARK, MARKEND, MARKENDC, FAILNEXT, SETFAIL and RESET) macros only work on the default heapfor the thread from which they are called.
If your test code is in a268DEBUG MACROS AND TEST CLASSESdifferent process to the code you’re testing, which is typical when testingclient–server code, the macros I’ve discussed won’t work. There are atleast three possible options here:• In addition to the __UHEAP_X macros, there is also a set of equivalent__RHEAP_X macros which perform checking on a specific heap,identified by an RHeap parameter.2 If you take a handle to theserver’s heap, you can pass it to these functions to check the server’sheap from a separate process. You can get a handle to the server’sheap by opening an RThread handle on the server’s main thread(using RThread::Open()) and then calling Heap() on this handleto acquire an RHeap pointer.• Add a debug-only test API to your server which calls the appropriate__UHEAP_X macro server-side. You can then define your own macroswhich call these methods:RMyServerSession::MarkHeap() calls __UHEAP_MARKRMyServerSession::MarkHeapEnd() calls __UHEAP_MARKENDRMyServerSession::FailNextAlloc() calls __UHEAP_FAILNEXTRMyServerSession::ResetHeap() calls __UHEAP_RESET• Compile the server code directly into your test executable rather thanrunning a client–server IPC framework.
This is necessary where youdon’t control the client–server API, but is a cumbersome solutionin general.The Symbian OS header file, e32def.h, also defines the__UHEAP_CHECK, __UHEAP_CHECKALL and __UHEAP_MARKENDCmacros. These are useful if you want to verify the number of objectscurrently allocated on the default heap. __UHEAP_CHECKALL takes aTInt parameter, checks it against the number of current heap allocations and raises a panic if the numbers differ.
__UHEAP_CHECK is similar,except it counts only those allocations since the last __UHEAP_MARK call.Finally, __UHEAP_MARKENDC has a similar role to __UHEAP_MARKEND,but rather than checking that nothing is left on the heap, it checks thatthe number of outstanding heap allocations matches the TInt parameterpassed to it.If you surround code which leaks heap memory with a pair of__UHEAP_MARK and __UHEAP_MARKEND macros, an ALLOC panic willbe raised by User::__DbgMarkEnd(), which is called by__UHEAP_MARKEND.
So what should you do if you encounter sucha panic? How do you debug the code to find out what was leaked so youcan fix the problem?2Incidentally, Symbian OS also provides a set of __KHEAP_X macros, which lookidentical to the __UHEAP_X macros, but can be used to check the kernel heap, for examplewhen writing device driver code.HEAP-CHECKING MACROS269The panic reason for the ALLOC panic contains a hexadecimal valuewhich indicates the address of the first orphaned heap cell. First of all, youshould try to get more information about the type of the object that wasleft orphaned on the heap.
I’m assuming that you can reproduce the panicusing the Windows emulator running in a debugger. With just-in-timedebugging enabled, as described in Chapter 15, you should run your testcode to the breakpoint just after the panic. You should then be able tofind the panic reason, expressed as an 8-digit value (e.g. c212d698). Ifyou type this value into a watch window, prefixing it with 0x, you’ll havea view of the orphaned heap cell.You may be able to recognize the object simply by inspecting thecontents of the memory in the debugger.
Otherwise, it’s worth castingthe address in the watch window to (CBase*) to see if you can get moreinformation about the object. If it’s a C class object, that is, CBase*derived, the cast allows the debugger to determine the class of the objectfrom its virtual function table pointer. Since C classes are always heapbased, there’s a fair chance that the orphaned object can be identified inthis way.
If there’s no information to be gleaned from a CBase* cast, youcan also try casting to HBufC* or any of the other classes that you knoware used on the heap in the code in question.Once you know the type of the orphaned object, that’s often sufficientto help you track down the leak. However, if there are numerous objectsof that type, or you can’t identify it (perhaps because the leak is occurringin a library which your code is using, rather than in your own code),you may need to use the debugger to add conditional breakpoints forthat particular address in memory. With these in place, when you runthe code again, it should break at the heap cell whenever it is allocated,de-allocated or modified.
You may find that the breakpoints are hit afew times, as the heap cell is allocated and deallocated, but it is onlythe last allocation that you’re interested in. When you’ve determinedthe point at which the memory is allocated for the last time, you caninspect the call stack to see what point you’re reached in your code.This should help you track down which object is being allocated, andlater orphaned, and thus resolve the leak.
You can find more informationabout how to use the debugger to find a memory leak on the SymbianDeveloper Network website (on www.symbian.com/developer, navigateto the Knowledgebase and search for ”memory leak”).It’s good practice to use the __UHEAP_X macros to verify thatyour code does not leak memory. You can put __UHEAP_MARK and__UHEAP_MARKEND pairs in your test code and, since the macrosare not compiled into release builds, you can use them to surroundareas where leaks may occur in your production code too.27017.2DEBUG MACROS AND TEST CLASSESObject Invariance MacrosThe __DECLARE_TEST and __TEST_INVARIANT macros allow you tocheck the state of an object, for example at the beginning and end ofa complex function.
The __DECLARE_TEST macro provides a standardway to add a function to a class that allows object invariance to be tested.This function is named __DbgTestInvariant() and you should defineit as necessary. When performing the invariance testing, rather than callthe function directly, you should use the __TEST_INVARIANT macro,as I’ll show shortly.__DECLARE_TEST is defined as follows in e32def.h:#if defined(__DLL__)#define __DECLARE_TEST public: IMPORT_C void __DbgTestInvariant()const; void __DbgTest(TAny *aPtr) const#else#define __DECLARE_TEST public: void __DbgTestInvariant() const;void __DbgTest(TAny *aPtr) const#endifYou should add it to any class for which you wish to perform an objectinvariance test.