Real-time C Programming Guidelines
Home Page | Applications Notes | SigLib User Contributed Files
If you find that your otherwise perfect C code crashes for no particular reason then the reason may be that the problem relates to memory management issues.
When learning about writing C programs many books and classes specify that all data that is local to a function should be declared within the function. This provides for a neater programming style that reduces problems caused by coding errors. Unfortunately, this is not the whole truth because local data is placed on the stack, which is a dynamic structure that grows and shrinks as required. This applications note describes how to get the most out of your compiler and linker tools to avoid unpredictable program execution.
The compiler will turn the C code into executable object code and this is passed to the linker, which will locate all of the code and data into the memory for the target processor. Locating program code is generally easy because the compiler will calculate how large the code space is and the linker will then place it correctly in memory. Data is altogether more complex because there are several different ways to access data. Although there are many different data types, they can all be split into three generic types :
The C source declaration of global variables is shown in figure 1.
|/* Start of source
/* Data and arrays allocated here will be placed in global variable space */
void function (void)
Figure 1 : Memory allocation is C source file
While the compiler and linker can calculate the size of the global variable space, memory allocation problems usually arise because of the stack and heap spaces due to the fact that they are dynamic and hence neither the compiler nor the linker can predict how large they are going to be nor where to locate them in memory.
Although there are many different allocation schemes in use, the same basic rules apply to all. Figure 2 shows a generic scheme where the stack grows from the bottom of memory up and the heap grows from the top down, with the global variable space located in the middle. Problems will occur in this scheme if either the stack or the heap spaces expand over the global variable space.
All compilers utilise pre-defined maximum heap and stack sizes that can usually be modified by the use of a command line parameter. If the dynamic memory allocation functions (e.g. malloc ()) are used and the return values checked then a lack of heap space will become obvious and can be worked around by modifying the linker options or the program code. The stack however does not check that there is enough space to allocate the required memory and if too much is requested then the first symptom is typically an errant pointer and a program crash..
Typically linkers allocate less that 10 KBytes for the stack and while for simple programs this is not an issue, for programs that are required to process a large amount of data, this will mean that the code must be written to take this into account. For a real time DSP application, data structures may often be larger than this, especially when floating point data is used (float is of size 4 bytes and double of size 8 bytes) so these must never be placed on the stack. The rules for managing the stack are therefore simple :
Thus we have distilled the majority of problems related to memory management down to removing all local arrays from functions. We will now look at the steps we can take to do this in a structured manner.
We basically have three types of memory to manage :
(The fouth possible option of static uninitialised is a perfectly valid option but the use of dynamically allocated data arrays leads to a more structured program).
There are two options for statically allocating look up tables, they can either be allocated outside of any function so that the data will be located in the global memory space or they can be declared locally within a function and the static key word used to specify that the data is placed in the global memory space. As a point of note, the first option can often be of most use in a DSP application because many look up tables are required in multiple functions (for example a constellation map may be required in both modulator and demodulator functions) so declaring them to be global can reduce duplication.
For dynamic memory, the solution is to use malloc to allocate the data arrays but this is a slow function to execute and not consistent with being included in a real time function. As a result it is common for all complex DSP functions to be accompanied by an initilasation function that can be called at boot time. This initialisation function will have the task of allocating memory and, if required, initialising the contents.
If you have tried all of these suggestions and your code still crashes then it may be that you will need to use a larger memory model. This allows for a larger memory space but will often mean that the code will run slower because memory paging will be used, rather than direct memory accesses.
Home Page | SigLib™ DSP Library | DFPlus™ Filter Designer
If you have
any comments or questions please email Numerix : firstname.lastname@example.org
Copyright © 2018, Sigma Numerix Ltd.. Permission is granted to create WWW pointers to this document. All other rights reserved. SigLib is a trademark of Sigma Numerix Ltd.. All trademarks acknowledged.