Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

USE STACK MEMORY CAREFULLY

325

code unloads. I describe how to use these in Chapter 17. Leaks can be difficult to track down, so it is far easier to test for them as part of the regular testing cycle, in order to catch regressions close to the time at which they are introduced.

21.3 Use Stack Memory Carefully

For a target mobile phone running Symbian OS, the program stack size defaults to 8 KB,4 and it doesn’t grow when that size is exceeded. Actually that’s not quite true, because the Symbian OS emulator running on Windows will extend the program stack if the size exceeds the limit. Consequently, you may find that your application runs perfectly well on the emulator, while it overflows the stack on hardware.

So what happens when you outgrow the stack space available? Further attempts to use the stack will result in access to a memory address that’s not mapped into the address space for your process. This causes an access violation that will generate an exception – at which point the kernel will panic your code with KERN-EXEC 3. In fact, if you try to build code where any single function tries to allocate automatic variables occupying more than 4 KB, you will receive an unresolved external __chkstk link error. However, this only considers static allocation and doesn’t take into account recursion, variable length looping or anything else that can only be calculated at runtime.

Both the size of the program stack, which is significantly smaller than most desktop applications have available, and the fact it does not grow dynamically, make it important that your stack usage is conservative.

Manage Recursive Code Carefully

Recursive code can use significant amounts of stack memory. To ensure that your code does not overflow the stack, you should build in a limit to the depth of recursion, where this is possible. It’s a good idea to minimize

4 For a component running in a separate process, e.g. a server or .exe, you can change the stack size from the default 8 KB, by adding the following to your. mmp file to specify a new stack size in hexadecimal or decimal format. For example:

epocstacksize 0x00003000 // 12 KB

Alternatively, you can create a new thread and specify a chosen stack size, as described in Chapter 10. This is particularly useful if your application makes heavy use of the stack, say through recursion, and needs more space than the default stack size makes available.

If you do hit the limit, it’s worth considering why your code is using so much stack space before assigning more, because consumption of extra stack resources leaves less free for other programs. There are valid reasons why you may need more stack, but you may alternatively wish to consider modifying the program architecture or employing some of the tips described here.

326

GOOD CODE STYLE

the size of parameters passed into recursive code and to move local variables outside the code.

Consider the Effect of Settings Data on Object Size

To minimize object size where a class has a large amount of state or settings data, you may be able to use bitfields to store it rather than 32-bit TBools or enumerations (in fact, this applies equally to stackand heap-based classes). A good example of this is the TEntry class which is part of F32, the filesystem module. A TEntry object represents a single directory entry on the filesystem, and keeps track of its attributes (such as whether it is marked read-only or hidden), its name and its size. The TEntry class is defined in f32file.h – it has a single TUint which stores the various attributes, each of which is defined in the header file as follows:

const TUint KEntryAttNormal=0x0000; const TUint KEntryAttReadOnly=0x0001; const TUint KEntryAttHidden=0x0002; const TUint KEntryAttSystem=0x0004; const TUint KEntryAttVolume=0x0008; const TUint KEntryAttDir=0x0010; const TUint KEntryAttArchive=0x0020;

...

class TEntry

{

public:

...

IMPORT_C TBool IsReadOnly() const;

IMPORT_C TBool IsHidden() const;

IMPORT_C TBool IsSystem() const;

IMPORT_C TBool IsDir() const;

IMPORT_C TBool IsArchive() const;

public:

TUint iAtt;

...

};

The iAtt attribute setting is public data, so you can perform your own checking, although the class also provides a set of functions for retrieving information about the attributes of the file. These are implemented something like the following:

TBool TEntry::IsReadOnly() const

{

return(iAtt&KEntryAttReadOnly);

}

TBool TEntry::IsHidden() const

{

return(iAtt&KEntryAttHidden);

}

USE STACK MEMORY CAREFULLY

327

This is all quite straightforward and transparent to client code because the function returns a boolean value, although it doesn’t store the attribute as such. As I’ve mentioned previously, you should aim to keep your class intuitive to use and maintain. While you may be able to shave a few bytes off the class by using bitfields, you need to consider the impact this may have on the complexity of your code, and that of your clients.

Use Scoping to Minimize the Lifetime of Automatic Variables

Simple changes to the layout of code can make a significant difference to the amount of stack used. A common mistake is to declare all the objects required in a function at the beginning, as was traditional in C code.

Consider the following example, which is flawed because it declares three large objects on the stack before they are all immediately necessary.

void CMyClass::ConstructL(const TDesC& aDes1, const TDesC& aDes2)

{

TLarge object1;

TLarge object2;

TLarge object3;

object1.SetupL(aDes1);

object2.SetupL(aDes2);

object3 = CombineObjectsL(object1, object2); iUsefulObject = FinalResultL(object3);

}

If either of the SetupL() functions fails, space will have been allocated and returned to the stack unnecessarily for at least one of the large objects. More importantly, once object1 and object2 have been combined to generate object3, it is unnecessary for them to persist through the call to FinalResultL(), which has an unknown stack overhead. By prolonging the lifetime of these objects, the code is wasting valuable stack space, which may prevent FinalResultL() from obtaining the stack space it requires.

A better code layout is as follows, splitting the code into two separate functions to ensure the stack space is only used as it is required, at the expense of an extra function call. Note that, because the parameters are all passed by reference, no additional copy operations are performed to pass them to the separate function.

void CMyClass::DoSetupAndCombineL(const TDesC& aDes1, const TDesC& aDes2, TLarge& aObject)

{

TLarge object1; object1.SetupL(aDes1); TLarge object2; object2.SetupL(aDes2);

328

GOOD CODE STYLE

aObject = CombineObjectsL(object1, object2);

}

void CMyClass::ConstructL(const TDesC& aDes1, const TDesC& aDes2)

{

TLarge object3;

DoSetupAndCombineL(aDes1, aDes2, object3); iUsefulObject = FinalResultL(object3);

}

In general, it is preferable to avoid putting large objects on the stack – where possible you should consider using the heap instead. If you make large objects member variables of C classes, you can guarantee that they will always be created on the heap.

21.4Eliminate Sub-Expressions to Maximize Code Efficiency

If you know that a particular function returns the same object or value every time you call it, say in code running in a loop, it is sensible to consider whether the compiler will be able detect it and make an appropriate optimization. If you can guarantee that the value will not change, but cannot be sure that the compiler will not continue to call the same function or re-evaluate the same expression, it is worth doing your own sub-expression elimination to generate smaller, faster code. For example:

void CSupermarketManager::IncreaseFruitPrices(TInt aInflationRate)

{

FruitSection()->UpdateApples(aInflationRate);

FruitSection()->UpdateBananas(aInflationRate);

...

FruitSection()->UpdateMangoes(aInflationRate);

}

In the above example, you may know that FruitSection() returns the same object for every call made in the function. However, the compiler may not be able to make that assumption and will generate a separate call for each, which is wasteful in terms of space and size. The following is more efficient, because the local variable cannot change and can thus be kept in a register rather than being evaluated multiple times:

void CSupermarketManager::IncreaseFruitPrices(TInt aInflationRate)

{

ELIMINATE SUB-EXPRESSIONS TO MAXIMIZE CODE EFFICIENCY

329

CFruitSection* theFruitSection = FruitSection();

theFruitSection->UpdateApples(aInflationRate);

theFruitSection->UpdateBananas(aInflationRate);

...

theFruitSection->UpdateMangoes(aInflationRate);

}

Consider the following example of inefficient code to access and manipulate a typical container such as an array. The function adds an integer value to every positive value in the iContainer member of class CExample (where iContainer is a pointer to some custom container class which stores integers).

void CExample::Increment(TInt aValue)

{

for (TInt index=0;index<iContainer->Count();index++)

{

if ((*iContainer)[index]>0)

(*iContainer)[index]+=aValue;

}

}

The compiler cannot assume that iContainer is unchanged during this function. However, if you are in a position to know that the functions you are calling on the container will not change it (that is, that Count() and operator[] do not modify the container object itself), it would improve code efficiency to take a reference to the container, since this can be stored in a register and accessed directly.

void CExample::Increment(TInt aValue)

{

CCustomContainer& theContainer = *iContainer;

for (TInt index=0;index<theContainer.Count();index++)

{

if (theContainer[index]>0)

theContainer[index]+=aValue;

}

}

Another improvement in efficiency would be to store a copy of the number of objects in the container rather than call Count() on each iteration of the loop. This is not assumed by the compiler, because it cannot be certain that the number of objects in iContainer is constant throughout the function. You, the programmer, can know if it is a safe optimization because you know that you are not adding to or removing from the array in that function.

(Rather than creating another variable to hold the count, I’ve simply reversed the for loop so it starts from the last entry in the container. This has no effect on the program logic in my example, but if you know you