1. Trang chủ
  2. » Công Nghệ Thông Tin

Symbian OS Explained Effective C++ Programming for Smartphones phần 8 potx

40 190 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 40
Dung lượng 312,64 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com... Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com... The macro can be used as follows:

Trang 1

252 PANICS

A solution is to run deliberately badly-behaved client code in aseparate test thread, programmatically checking the resulting exit reasonsand categories of the panicked thread against those you would expect

to have occurred You should disable just-in-time debugging for theduration of the test, so that only the test thread, rather than the emulator,

is terminated For example:

enum TChilliStrength {

ESweetPepper, EJalapeno, EScotchBonnet };

void EatChilli(TChilliStrength aStrength) {

_LIT(KTooStrong, "Too Strong!");

ASSERT_ALWAYS(EScotchBonnet!=aStrength, User::Panic(KTooStrong, KErrAbort);

// Omitted for clarity }

// Thread function TInt TestPanics(TAny* /*aData*/) {// A panic occurs if code is called incorrectly EatChilli(EScotchBonnet);

return (KErrNone);

}

void TestDefence() {

// Save current just-in-time status TBool jitEnabled = User::JustInTime();

// Disable just-in-time debugging for this test User::SetJustInTime(EFalse);

_LIT(KPanicThread, "PanicThread");

// Create a separate thread in which to run the panic testing RThread testThread;

TInt r = testThread.Create(KPanicThread, TestPanics,

KDefaultStackSize, NULL, NULL);

Trang 2

SUMMARY 253

15.5 Faults, Leaves and Panics

A fault is raised if a critical error occurs such that the operating systemcannot continue normal operation On hardware, this results in a reboot

A fault can only occur in kernel-side code or a thread which is essential tothe system, for example the file server, so typically you will not encounterthem unless you are writing device drivers or uncover a bug in the OS Ineffect, a fault is another name for a serious system panic

Chapter 2 discusses leaves in more detail, but, in essence, they occurunder exceptional conditions such as out of memory or the absence of acommunications link Unlike a panic, a leave should always be caught(”trapped”) by code somewhere in the call stack, because there shouldalways be a top-level TRAP However, if a leave is not caught, this impliesthat the top-level TRAP is absent (a programming error) and the threadwill be panicked and terminate

15.6 Summary

This chapter discussed the use of panics to terminate the flow of execution

of a thread Panics cannot be ”caught” like an exception and are severe,resulting in a poor user experience For that reason, panics are only useful

to track down programming errors and, on Symbian OS, are typicallycombined with an assertion statement, as discussed in the next chapter.This chapter described the best way to identify panics and illustratedhow to test the panics that you’ve added to your own code for defensiveprogramming It gave a few examples of commonly-encountered systempanics and directed you to the Panics section of the system documentationfor a detailed listing of Symbian OS system panics

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 4

exclamation above, and died.

In C++, assertions are used to check that assumptions made about codeare correct and that the state, for example, of objects, function parameters

or return values, is as expected Typically, an assertion evaluates astatement and, if it is false, halts execution of the code and perhapsprints out a message indicating what failed the test, or where in code thefailure occurred

On Symbian OS, you’ll find the definition of two assertion macros1ine32def.h:

#define ASSERT_ALWAYS(c,p) (void)((c)||(p,0))

1 At first sight, these definitions seem more complex than they need to be, when the following simpler definition could be used:

#define ASSERT_ALWAYS(c,p) ((c)||(p))

The reason for the (p,0) expression is for cases where the type returned from p is void (the case when p is a typical Panic() function) or a value that can’t be converted to an integer type for evaluation The cast to void is present to prevent the return value of the expression being used inadvertently.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 5

256 BUG DETECTION USING ASSERTIONS

You’ll notice that the assertion macros do not panic by default, butallow you to specify what procedure to call should the assertion fail.This gives you more control, but you should always terminate the runningcode and flag up the failure, rather than return an error or leave Assertionshelp you detect invalid states or bad program logic so you can fix yourcode as early as possible It makes sense to stop the code at the point

of error, thus forcing you to fix the problem (or remove the assertionstatement if your assumption is invalid) If the assertion simply returns anerror on failure, not only does it alter the program flow, but it also makes

it harder to track down the bug

You should always raise a panic when an assertion statement fails.

16.1 −−−ASSERTDEBUG

Here’s one example of how to use the debug assertion macro:

void CTestClass::TestValue(TInt aValue) {

#ifdef _DEBUG _LIT(KPanicDescriptor, "TestValue"); // Literal descriptor

#endif ASSERT_DEBUG((aValue>=0), User::Panic(KMyPanicDescriptor, KErrArgument));

}

Of course, this is somewhat awkward, especially if you expect to use

a number of assertions to validate your code, so you’ll probably define apanic utility function for your module, with its own panic category stringand a set of panic enumerators specific to the class So, for example, you’dadd the following enumeration to CTestClass, so as not to pollute theglobal namespace:

enum TTestClassPanic {

EInvalidData, // =0 EUninitializedValue // =1

Trang 6

You could then write the assertion in TestValue() as follows:

void CTestClass::TestValue(TInt aValue) {

enu-to locate the appropriate class and use the panic category enumeration enu-tofind the associated failure, which you will have named and documentedclearly to explain why the assertion failed

There may be cases where there’s nothing more a client programmercan do other than report the bug to you, the author of the code;alternatively, the problem could be down to their misuse of your library,which they’ll be able to correct I’ll discuss the pros and cons of usingassertions to protect your code against badly-programmed calling codelater in this chapter

If you don’t want or need an extensive set of enumerated panic values,and you don’t expect external callers to need to trace a panic, you mayconsider using a more lightweight and anonymous assertion A goodexample of this is to test the internal state of an object, which couldnot possibly be modified by an external caller, and thus should always

be valid unless you have a bug in your code Assertions can be addedearly in the development process, but left in the code, in debug builds, tovalidate the code as it is maintained and refactored In these cases, youmay consider using the ASSERT macro, defined in e32def.h as follows:

#define ASSERT(x) ASSERT_DEBUG(x, User::Invariant())

I like this macro because it doesn’t need you to provide a paniccategory or descriptor If condition x is false, in debug builds only, itcalls User::Invariant() which itself panics with category USER andreason 0 The macro can be used as follows:

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 7

258 BUG DETECTION USING ASSERTIONS

ASSERT(iClanger>0);

As an alternative to using ASSERT to test the internal state of yourobject, you may wish to consider using the TEST_INVARIANT macro,which I discuss in more detail in Chapter 17

An alternative, useful definition of ASSERT which you may see insome Symbian OS code is as follows:

#ifdef _DEBUG

#ifdef ASSERT

#undef ASSERT

#endif

#define ASSERT_FILE (s) _LIT(KPanicFileName,s)

#define ASSERT_PANIC (l) User::Panic(KPanicFileName().Right(12),l)

#define ASSERT(x) { ASSERT_FILE ( FILE );

ASSERT_DEBUG(x, ASSERT_PANIC ( LINE ) ); }

#endif

This slightly alarming construction is actually quite simple; in debugbuilds, if condition x is false, the code is halted by a panic identifyingthe exact place in code (in the panic descriptor – which contains the last

12 characters of the filename) and the panic category (which containsthe line of code at which the assertion failed) The disadvantage ofusing this construct is that you are coupling the compiled binary directly

to the source file You cannot later modify your code file, even tomake non-functional changes to comments or white space lines, withoutrecompiling it to update the assertion statements The resulting binarywill differ from the original, regardless of the nature of the changes.Depending on how you deliver your code, this limitation may prohibityou from using this macro

Let’s move on from how to use the Symbian OS assertion syntax toconsider when you should use assertions and, perhaps more importantly,when you should not

Firstly, don’t put code with side effects into assertion statements Bythis, I mean code which is evaluated before a condition can be verified.For example:

pro-Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 8

−−−ASSERT−DEBUG 259

assertion You should follow this rule for both ASSERT_DEBUG and ASSERT_ALWAYSstatements, despite the fact that the latter are com-piled into release code, because, while you may initially decide theassertion applies in release builds, this may change during the develop-ment or maintenance process You could be storing up a future bug forthe sake of avoiding an extra line of code

You must also make a clear distinction between programming errors(”bugs”) and exceptional conditions Examples of bugs might be contra-dictory assumptions, unexpected design errors or genuine implementationerrors, such as writing off the end of an array or trying to write to a filebefore opening it These are persistent, unrecoverable errors which should

be detected and corrected in your code at the earliest opportunity Anexceptional condition is different in that it may legitimately arise, although

it is rare (hence the term ”exceptional”) and is not consistent with typical

or expected execution It is not possible to stop exceptions occurring,

so your code should implement a graceful recovery strategy A goodexample of an exceptional condition that may occur on Symbian OS is anout-of-memory failure, because it is designed to run constantly on deviceswith limited resources for long periods of time without a system reset

To distinguish between bugs and exceptions, you should consider thefollowing question Can a scenario arise legitimately, and if it can, isthere anything you should or could do to handle it? If your answer is

”yes”, you’re looking at an exceptional condition – on Symbian OS, this

is exhibited as a leave (leaving is discussed in Chapter 2) If the answer

is ”no”, you should consider the situation to be caused by a bug whichshould be tracked down and fixed The rest of this chapter will focus onthe use of assertions to highlight such programming errors

When code encounters a bug, it should be flagged up at the point

at which it occurs so it can be fixed, rather than handled or ignored(which can at best complicate the issue and, at worst, make the bugmore difficult to find or introduce additional defects as you ”code aroundit”) You could consider assertions as an annoying colleague, leaningover your shoulder pointing out defects for you as your code runs Theydon’t prevent problems, but make them obvious as they arise so you can

fix them

If you add assertion statements liberally as you write code, theyeffectively document assumptions you make about your program logicand may, in addition, flag up unexpected problems As you considerwhich assertions to apply, you are actually asking yourself what implicitassumptions apply to the code and how you can test them By thinkingabout each piece of code you write in this way, you may well discoverother conditions to test or eliminate that would not have been immediatelyobvious Frequent application of assertions as you code can thus helpyou to pre-empt bugs, as well as catch those already in existence

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 9

260 BUG DETECTION USING ASSERTIONS

And there’s more! Another benefit of assertions is the confidence thatyour code is behaving correctly, and that you are not ignoring defectswhich may later manifest themselves where they are hard to track down,for example intermittently or through behavior seemingly unrelated to thecode that contains the error Say you write some library code containing

no assertions and then create some code to test it, which runs and returns

no errors; are you confident that everything behaves as you expect andthe test code checks every boundary condition? Are you sure? Certain?Adding assertions to test fundamental assumptions in your code willshow you immediately if it is swallowing or masking defects – and itshould give you more confidence that the test results are valid Sure, youmight hit some unpleasant surprises as the test code runs for the firsttime, but once you’ve ironed out any failures, you can be more confidentabout its overall quality What’s more, the addition of assertions alsoprotects your code against any regressions that may be introduced duringmaintenance and refactoring but which would not otherwise be picked

up by your test code What more could you ask?

The cases so far could be considered as ”self defense”, in that I’vediscussed using assertions to catch bugs inyour code Let’s move on toconsider defensive programming in general Defensive programming isnot about retorting ”It works OK on my machine” after being informedthat your code doesn’t work as expected It’s based on defending yourcode against irresponsible use or downright abuse by code that calls

it Defensive code protects functions against invalid input, by inspectingdata passed in and rejecting corrupt or otherwise flawed parameters, such

as strings that are too long or out-of-range numerical values

You’ll need to consider how to handle bad parameters depending onhow your code is called; for example, you may want to assert that thedata is good, terminating with a panic if it is not Alternatively, you maydecide to continue the flow of execution, so instead of assertions, you’llcheck each incoming parameter (e.g using if statements) and return

to the caller if invalid data is detected – either with an error value or aleave code Another method would be to check incoming data and, if

a parameter is invalid, substitute it with a default parameter or continuewith the closest legal value What you don’t want to do is ignore invalidinput and carry on regardless, since this could lead to problems later

on, such as data corruption Whatever method you use to handle illegalinput, it should be consistent throughout your code

Your clients should be testing with debug versions of your librariesand thus you could use ASSERT_DEBUG statements to alert them ofinvalid input, or other misuse, so they can correct it

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 10

−−−ASSERT−ALWAYS 261

ASSERT_DEBUG assertions can be added early in the ment process to highlight programming errors and can be left in to validate the code as it is maintained and refactored – acting as a means to ”design by contract”.

develop-16.2 −−−ASSERTALWAYS

You still need to be defensive by checking for illegal usage in releasebuilds too, but the case for using ASSERT_ALWAYS isn’t clear cut.Remember, your assertions will terminate the flow of execution andpanic the library, displaying a nasty ”program closed” dialog to the user,which is generally best avoided where possible Additionally, you shouldconsider the impact on the speed and size of your code if you applyassertion statements liberally in release builds

If you decide not to use ASSERT_ALWAYS to check incoming values,you should use another defensive technique to guard against illegal input,such as a set of if statements to check values and return error codes orleave when data is unusable You could use these in combination with

a set of ASSERT_DEBUG statements to alert the client programmer toinvalid use in debug builds, but often it is preferable to keep the flow

of execution the same in both debug and release builds In such cases,

I suggest you don’t use debug assertions to check input, but instead use

if statement checking in both modes, and document each expectedreturn value for your functions Client programmers should understandtheir responsibility to interpret the return value and act accordingly I’llillustrate this with an example later in this chapter

To determine whether you should use ASSERT_ALWAYS or another,less terminal, defense, I recommend that you consider whether the callingcode may be able to take a different action if you do return an error Invalidinput is a bug from the perspective of your code, but may be caused by

an exceptional condition in the calling code which can be handled

A simplistic example would be a call to your code to open and write to

a file, where the caller passes in the full file name and path, as well as thedata to be written to the file If the file does not exist, it is probably moreappropriate to return this information to the caller through a returnederror code or leave value than to assert in a release build Client codecan then anticipate this and deal with it, without the need for your library

to panic and alarm the user accordingly

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 11

262 BUG DETECTION USING ASSERTIONS

Here’s an example of this scenario which illustrates how to defendagainst illegal parameters without assertions This code returns an error toallow the caller to recover if they pass in an invalid parameter Of course,

if the calling code is written in such a way that each parameter should becorrect and that only a bug could result in them being invalid, it can assertthat the return value from a call to WriteToFile() is KErrNone Onthe other hand, if it’s an exceptional circumstance that the file is missing

or the data is non-existent, it can handle it gracefully

TInt CTestClass::WriteToFile(const TDesC& aFilename, const TDesC8& aData)

{ TInt r = KErrNone;

if (KNullDesC8==aData) {// No data to write – invalid!

r = KErrArgument;

} else { RFile file;

ASSERT_DEBUG(iFs, Panic(EUninitializedValue));

r = file.Open(iFs, aFilename, EFileWrite);

if (KErrNone==r) { // Only executes if the file can be opened // Writes aData to file, closes file etc }

up the problem so it can be fixed A good example of this is in theSymbian OS array classes (RArray and RPointerArray), which have ASSERT_ALWAYSguards to prevent a caller passing an invalid index tomethods that access the array The class provides functions to determinethe size of the array, so if a caller attempts to write off the end of thearray, it can only be doing so because of a bug

Likewise, in the code above, if the context of the function means thatthe second parameter, aData, should never be an empty string, youcan replace the first if statement check with an ASSERT_ALWAYSstatement But this assumes knowledge of how clients expect to call it

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 12

to do so, depending on the circumstances in which it is used Whateverthe decision, ignoring the problem of illegal input is not an option!

Use defensive coding techniques to protect your functions against invalid input from calling code ASSERT_ALWAYS should be used to protect against illegal input that can only ever have arisen through a bug in the caller.

16.3 Summary

This chapter discussed assertion statements on Symbian OS in terms ofhow they work, what they do and how you should use them Theirprimary purpose is for you to verify program logic as you write code and

to detect bugs at the point they occur so you can find and fix them Forthis reason, assertion statements must terminate the flow of executionupon failure, typically with a panic

The chapter recommended using ASSERT_DEBUG or ASSERT ments liberally in your code to check its internal state You should expect

state-to test your code thoroughly and fix defects before release, so youshouldn’t need to use ASSERT_ALWAYS to check the internals of yourcode A user, or another software developer using your code, expectsyou to have debugged your code; they don’t want to do it for you Fur-thermore, you should think carefully before adding assertion statements

to release code because of the added code size and extra executionoverhead associated with them

You should make the distinction between bugs and exceptions (failuresoccurring under exceptional circumstances) While you can use assertions

to catch bugs, you should handle exceptions gracefully, since they mayoccur legitimately in your code, albeit as an atypical path of execution.This point extends to your client’s code – you should program defensivelyand consider whether invalid input has arisen because of a bug orexception in the caller If it can only be due to a bug, it is acceptable touse ASSERT_ALWAYS statements in your code to indicate to the clientthat they have a programming error which needs fixing However, sincerelease build assertion statements have a cost in terms of size, speed and

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 13

264 BUG DETECTION USING ASSERTIONS

ugly code termination, I advise you to consider carefully before usingthem against invalid input data which may have arisen from an exceptionthat the caller can handle more gracefully

This chapter illustrated some aspects of defensive programming besidesthe use of assertion statements, but added this note of caution You shouldconsider carefully how much defensive code to use, and whether it variesbetween debug and release builds It can create additional complexity

in your code, leaving it open to its own set of bugs, and, if you checkparameter data for every possible error, it can also make your code slowand bloated You should take care to use defensive techniques wherethey are most effective, and document them clearly so the client canbuild their own bug catching and exception handling around them

The paradox is that you want problems to be noticeable so they areflagged up during development, but you want them to be inconspicuous

in your production code You should consider your approach to defensivecode and assertions appropriately for each project you work on In allcases, it’s best to keep it consistent and consider error handling as animportant issue to be determined and defined at an architectural level.The next chapter discusses useful debug macros and test classes onSymbian OS for tracking down programming errors such as memory leaksand invalid internal state You can find more information about handlingleaves (Symbian OS exceptions) in Chapter 2

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 14

Debug Macros and Test Classes

If you have built castles in the air, your work need not be lost; that is where they should be Now put foundations under them

Henry David Thoreau(Walden)

Memory management is an important issue on Symbian OS, which has anumber of mechanisms to assist you in writing memory leak-proof code.This chapter starts by discussing a set of debug macros that allow you tocheck that your code is managing heap memory correctly (you will findthem documented in your SDK under ”Memory Allocation”) It will alsodiscuss other useful debug macros and the RTest and RDebug classes

17.1 Heap-Checking Macros

There are macros which can be used to check that you are not leakingany memory under normal conditions and others that can be used tosimulate out-of-memory (OOM) conditions It is advisable to check yourcode using each macro type, to ensure both that it is leave-safe and that

it can handle OOM gracefully (that is, to ensure that heap memory is notorphaned by a leave occurring under OOM as I discussed in Chapter 2).This kind of testing is known as ”Alloc Heaven Testing”

Here are the macros, as defined in e32def.h They are only compiledinto debug builds, and are ignored by release builds, so they can be left

in your production code without having any impact on the code size

or speed

#define UHEAP_MARK User:: DbgMarkStart(RHeap::EUser)

#define UHEAP_MARKEND User:: DbgMarkEnd(RHeap::EUser,0)

#define UHEAP_MARKENDC(aCount) User:: DbgMarkEnd(RHeap::EUser,aCount)

#define UHEAP_FAILNEXT(aCount) User:: DbgSetAllocFail(RHeap::EUser,RHeap::EFailNext,aCount)

#define UHEAP_SETFAIL(aType,aValue) User:: DbgSetAllocFail(RHeap::EUser,aType,aValue)

#define UHEAP_RESET

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 15

266 DEBUG MACROS AND TEST CLASSES

The UHEAP_MARK and UHEAP_MARKEND macros verify thatthe default user heap is consistent The check is started by using UHEAP_MARKand a following call to UHEAP_MARKEND performsthe verification If any heap cells have been allocated since UHEAP_MARKand not freed back to the heap, UHEAP_MARKEND willcause a panic.1These macros can be nested inside each other and usedanywhere in your code You must take care to match the pairs, because

a UHEAP_MARKEND without a prior UHEAP_MARK causes a panic. UHEAP_FAILNEXTsimulates a heap failure (i.e out of memory).The macro takes a TInt parameter which specifies which memoryallocation call will fail (if the parameter is 1, the first heap allocation fails,

if it’s 2, the second allocation fails, and so on) The UHEAP_RESETmacro cancels the failure checking

Consider the following example, where a method thingL() makes several memory allocations internally 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:

CTest::Some-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 should

go 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 testing

1 The 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 the first orphaned heap cell I’ll discuss how to track down what is causing a panic of this type shortly.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 16

HEAP-CHECKING MACROS 267

TRAPD(r, test->SomethingL());

UHEAP_MARKEND; // Panics if the heap memory is leaked

// Stop the loop if r! =KErrNoMemory

if (KErrNoMemory!=r) {

User::LeaveIfError(r);

break; // For cases where r==KErrNone }

} CleanupStack::PopAndDestroy(test);

at 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 expanded

as 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 advisable

to 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, NEXT, SETFAIL and RESET) macros only work on the default heapfor the thread from which they are called If your test code is in a

FAIL-Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 17

268 DEBUG MACROS AND TEST CLASSES

different 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 handle

to acquire an RHeap pointer

• Add a debug-only test API to your server which calls the appropriate UHEAP_Xmacro server-side You can then define your own macroswhich call these methods:

RMyServerSession::MarkHeap() calls UHEAP_MARK RMyServerSession::MarkHeapEnd() calls UHEAP_MARKEND RMyServerSession::FailNextAlloc() calls UHEAP_FAILNEXT RMyServerSession::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 solution

in 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 alloca-tions 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_MARKand UHEAP_MARKEND macros, an ALLOC panic will

be raised by User:: DbgMarkEnd(), which is called by UHEAP_MARKEND So what should you do if you encounter such

a panic? How do you debug the code to find out what was leaked so youcan fix the problem?

2 Incidentally, Symbian OS also provides a set of KHEAP_X macros, which look identical to the UHEAP_X macros, but can be used to check the kernel heap, for example when writing device driver code.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 18

HEAP-CHECKING MACROS 269

The 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 have

a 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 heap-based, 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 sufficient

to help you track down the leak However, if there are numerous objects

of that type, or you can’t identify it (perhaps because the leak is occurring

in 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 (onwww.symbian.com/developer, navigate

to the Knowledgebase and search for ”memory leak”)

It’s good practice to use the UHEAP_X macros to verify that your code does not leak memory You can put UHEAP_MARK and UHEAP_MARKEND pairs in your test code and, since the macros are not compiled into release builds, you can use them to surround areas where leaks may occur in your production code too.

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Trang 19

270 DEBUG MACROS AND TEST CLASSES

17.2 Object Invariance Macros

The DECLARE_TEST and TEST_INVARIANT macros allow you tocheck the state of an object, for example at the beginning and end of

a 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 define

it 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_TESTis 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

#endif

You should add it to any class for which you wish to perform an objectinvariance test It’s best to add it to the bottom of the class declarationbecause it uses the public access specifier There are separate definitionsfor DLLs and EXEs because the function must be exported from a DLL so

it may be called from a separate module The second function defined

by DECLARE_TEST, DbgTest(), is designed to allow test code

to access non-public members of the class, if such testing is required.Because any test code will implement this function itself, it is notexported

The DECLARE_TEST macro is defined to be the same for bothrelease and debug builds, because otherwise the export list for debugbuilds would have an extra function ( DbgTestInvariant()) com-pared to the release version This could cause binary compatibilityproblems, by changing the ordinal numbers of other exported functions

in the module (Chapter 18 discusses binary compatibility in more detail.)The TEST_INVARIANT macro is defined below; as you can see,

it doesn’t call DbgTestInvariant() in release builds However, ifyou don’t want the invariance test code to be built into release builds, youshould surround it with preprocessor directives as appropriate to buildthe invariance testing into debug builds only

Trang 20

OBJECT INVARIANCE MACROS 271

The invariance test function, DbgTestInvariant(), typicallychecks that the object’s state is correct at the beginning and end of

a function If the state is invalid, a panic should be raised by callingUser::Invariant() (which panics with category USER, reason 0).You may recall, from Chapter 16, that this is the panic raised by theASSERTmacro Thus a simple way to implement the invariance testing

in DbgTestInvariant() is to perform the invariance checks insideASSERTstatements

Here’s an example of a class that represents a living person and usesthe DECLARE_TEST invariance test to verify that:

• the person has a gender

• if the name is set, it is a valid string of length no greater than

250 characters

• the given age in years matches the age calculated for the given date

of birth and is no greater than 140 years

class CLivingPerson: public CBase {

CLivingPerson::CLivingPerson(TGender aGender) : iGender(aGender) {}

CLivingPerson:: ∼CLivingPerson() {delete iName;}

CLivingPerson* CLivingPerson::NewL(const TDesC& aName, CLivingPerson::TGender aGender)

{ CLivingPerson* me = new (ELeave) CLivingPerson(aGender);

CleanupStack::PushL(me);

me->ConstructL(aName);

Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com

Ngày đăng: 13/08/2014, 08:21