Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 56
Текст из файла (страница 56)
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 witha set of __ASSERT_DEBUG statements to alert the client programmer toinvalid use in debug builds, but often it is preferable to keep the flowof 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 useif 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 byan exceptional condition in the calling code which can be handled.A simplistic example would be a call to your code to open and write toa 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 libraryto panic and alarm the user accordingly.262BUG DETECTION USING ASSERTIONSHere’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 missingor 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}}return (r);}You’ll notice that I’ve included an __ASSERT_DEBUG statement toverify internal state in my code and catch any defects (such as attemptingto use the file server handle before it has been initialized) in the test phase.One case where you may prefer to use __ASSERT_ALWAYS to protectyour code against illegal input is where that input could only havearisen through a bug in calling code and will cause ”bad things”,such as memory corruption, to occur.
You could return an error tothe caller, but it’s probably clearer for the calling code if you flagup the problem so it can be fixed. A good example of this is in theSymbian OS array classes (RArray and RPointerArray), which have__ASSERT_ALWAYS guards 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 itSUMMARY263and reduces the option for reuse at a later stage, should this condition nolonger apply.If the caller is passing data from a source that it does not directlycontrol, say a communications link, there is always a possibility forinvalid input to your code.
In these circumstances, it’s better to handlebad incoming data by returning an error or leaving. It is unusual to useassertions under these conditions, although code may occasionally needto 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 againstinvalid input from calling code.
__ASSERT_ALWAYS should beused to protect against illegal input that can only ever have arisenthrough a bug in the caller.16.3 SummaryThis 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 andto 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 statements liberally in your code to check its internal state.
You should expectto 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. Furthermore, you should think carefully before adding assertion statementsto 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 assertionsto 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 and264BUG DETECTION USING ASSERTIONSugly 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 complexityin 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 inconspicuousin 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.17Debug Macros and Test ClassesIf you have built castles in the air, your work need not be lost; that iswhere they should be. Now put foundations under themHenry 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 MacrosThere 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 thatit 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 leftin your production code without having any impact on the code sizeor 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_RESETUser::__DbgSetAllocFail(RHeap::EUser,RHeap::ENone,1)266DEBUG MACROS AND TEST CLASSESThe __UHEAP_MARK and __UHEAP_MARKEND macros verify thatthe default user heap is consistent. The check is started by using__UHEAP_MARK and a following call to __UHEAP_MARKEND performsthe verification.
If any heap cells have been allocated since__UHEAP_MARK and not freed back to the heap, __UHEAP_MARKEND willcause a panic.1 These macros can be nested inside each other and usedanywhere in your code. You must take care to match the pairs, becausea __UHEAP_MARKEND without a prior __UHEAP_MARK causes a panic.__UHEAP_FAILNEXT simulates 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 CTest::SomethingL() makes several memory allocations internally.