Start me up

March 11, 2004

MurphysLaw-March 11, 2004

Variables often pick up the wrong value during initializations, producing hard-to-find bugs. Here are tips for getting the numbers right.

Recently I've had more than my fair share of bugs to fix. In fact my rate of fixing bugs has been very high—a statistic I doubt is flattering. If I'm fixing that many bugs then I probably wrote them in the first place.

Quite a few of these bugs were in the start-up code and related to the initial values of various variables. After some poking around, I found that there's more to variable initialization than simply giving each one an initial value when it is declared.

Here's what I discovered and why it will help you avoid similar bugs during initializations.

Local variables
First let's consider initialization within a single function. Later we'll look at initialization of data that has a longer lifetime.

If you declare a local variable that takes multiple paths through if-then statements before the value is used, some paths may initialize that variable and others may not. I've found many compilers will warn of a problem when processing the following code fragment:

void foo(void)
{
  int x;
  if (x>5)
  {

The value of x the first time it's used is not known. Behavior at run time will be inconsistent because the value ofx depends on whatever data happens to be in the location on the stack used by x.

The same compilers, however, won't warn you when the variable may or may not get initialized. Consider the example in Listing 1 where the valuex is altered depending on the state of a global variable.

Listing 1: Value x altered by global variable

void foo(void)
{
  int x;
  if (someGlobal == TRUE)
  {
    x=6;
  }
  if (x>5)
  {

On my current project the uninitialized value was often valid because a fluke of the memory map meant that the location ofx was the same on each call and that the variable picked up the value ofx from the previous call. So every time I examinedx with the debugger, it looked reasonable. Thus the worst value for an uninitialized variable to take is the correct value, since that's the value that makes the bug hardest to spot.

While your compiler is unlikely to warn you when some paths fail to initialize the value, the PC-Lint tool from Gimpel Software (www.gimpel.com) will. This particular warning has saved my skin a couple of times. PC-Lint can output an avalanche of warnings, however, making it difficult to assess which ones represent real bugs and which are identifying bad style. When this happens I do a quick search for certain warnings. The warning of uninitialized values almost always represents a real problem in the code.

Similar problems arise when a variable is declared at the top of a function but not used until the later portion of the function. The problem is that the name is valid for the entire function, but the value is only valid for part of the function. Later someone might add a line near the top of the function that uses that variable. While the variable name is valid, the value it contains may not be. For this reason C++ allows us to declare variables at any point in the function—we can avoid declaring the value until we have a good initial value to put in it. If someone tries to use the variable too early in the function—before the declaration and initialization—the code will fail to compile. Keeping the declaration and initialization close to each other will minimize the possibility of using an uninitialized variable.

While C doesn't allow us to declare a variable at an arbitrary point in a function, we can declare variables within inner blocks of that function, which sometimes allows us to limit the scope of the name to only the part of the function where the variable has a valid value.

Ideally the name of a variable should never be valid unless the contents are valid. While we can't always reach this ideal in C, it's worth trying to get a bit closer.

Global variables
The system I'm working on has a large number of global variables. Though globals are known to increase the likelihood of bugs, you often can't avoid using them. On an 8-bit system protecting every value with a function-call interface can be expensive in terms of code size and execution speed. Sometimes the variables I use are file static, which restricts their visibility to the current module, but that only partially solves the problems we're about to look at.

When you forget to initialize a global value, the chances are you'll get a zero—though this isn't a certainty. The C standard dictates that any static values that aren't explicitly initialized in the code must be initialized to zero, but nonstatic values are undefined. It's a bad idea, however, to write code that depends on the static values being initialized to zero, since a design change might later switch the variable from static to nonstatic. You couldn't be certain that the programmer making such a change would realize that not only is the scope of the variable changing but its initial value could also change.

Whether the scope of the variable is one file or an entire system, there's a point in time when the memory is allocated and, later, a point when that value gets a valid initial value. If the value gets used in between these two times, we have a bug.

The easy fix is to give the variable a value in its declaration. The allocation of space and the assignment of the initial value will both happen before main() is called. Unfortunately, you don't always have a reasonable initial value to use.

Consider a system that can read a set of sensors, but the number of sensors in the set varies. On start-up, in a function called CountSensors(), the system checks to see how many sensors are connected and stores that value in G_numberOfSensors. Functions such as ReadAllSensors() use the number of sensors. It's important that the program never calls ReadAllSensors() before the number of sensors is known.

It should be simple to call CountSensors() before ReadAllSensors(). In practice the start-up sequence of many programs is long and involved. The order of calling the full set of initialization functions may change many times during development. Let's assume there's a function in this example called InitializeTemperatures() that uses ReadAllSensors(). As long as InitializeTemperatures() is called after CountSensors() everything is fine. If a problem occurs in which the temperatures aren't initialized early enough, we might end up moving the InitializeTemperatures() function to an earlier point in start-up without realizing we're violating the rule that CountSensors() must be called before ReadAllSensors().

One approach to this problem is to set the global G_numberOfSensors to an invalid value when it's declared and check that the value is valid in ReadAllSensors() using an assert. (I've explained this process in an earlier column.)1 This technique could guarantee a system failure if the start-up value were wrong, rather than continuing with data of dubious quality. If you're using G_numberOfSensors in a lot of places, you may find that adding all of the checks is tedious, but it may save a lot of time in the long run.

Arrays and structures
Blocks of memory, structures, or arrays can suffer from the same issues as numeric values when it comes to initialization. Consider a set of communications buffers in a linked list, where each buffer has one byte that indicates whether the buffer is free or in use. It's important to initialize the pointers from one buffer to the next and to initialize the in-use flag before any communications begins. In this design the buffers will only ever be accessed by iterating through the linked list. Here's how to set this up.

You'll want to initialize the linked list of buffers so that each buffer points to the next. You don't really need a global declaration of each buffer. One approach is to malloc() the space on a heap for each of the buffers and add each new buffer to the list. This means that the buffers are put on the list immediately after being created, and you avoid having a global name for each buffer, which might have allowed access to the list in a way not intended.

Many embedded systems developers shy away from the C heap and malloc() for reasons I've discussed elsewhere.2, 3 In this example, we don't really want a heap—we're just looking for a clean way to initialize our memory, which we don't intend to free for reuse. I find that communications and GUI projects involve lots of structures and buffers, and on those projects the following techniques are useful.

Listing 2: A static allocator

#define SALLOC_BUFFER_SIZE 90000
static unsigned char GS_sallocBuffer[SALLOC_BUFFER_SIZE];

static Boolean FS_enabled = TRUE;
int GS_sallocFree = 0;

void *salloc(int size)
{
    void *nextBlock;
    assert(FS_enabled);
    if(GS_sallocFree + size > SALLOC_BUFFER_SIZE)
        assert(FALSE);     nextBlock = &GS_sallocBuffer[GS_sallocFree];
    GS_sallocFree += size;
    return nextBlock;
}

void sallocDisable(void)
{
    FS_enabled = FALSE;
}

Listing 2 shows a simple allocator that allows blocks of memory to be allocated in the same fashion as malloc(). I call my routine salloc(), meaning static allocator. The allocator uses a simple array as the pool from which smaller pieces are handed out as requested in calls to the salloc() routine. This routine provides memory at initialization time, so when the start-up sequence is complete the sallocDisable() routine is called to set a flag that prevents the allocator from being used during normal operation of the system. This averts the pool of memory from being gradually used up during normal operation, avoiding the system failure that can occur when no memory remains.

Be aware that the code I show here is not reentrant and so requires some form of locking if used in an RTOS environment. Also note that the code doesn't allow for processors that require memory alignment. If your processor demands that some of its data types begin on even boundaries, you'll need to modify the code to allow for that. The most straightforward way is to round up the value of size to a multiple of two (or four if your alignment requirement is for addresses on boundaries that are multiples of four).

When I use this allocator with structures I often write a routine for each structure that calls the allocator and then populates the values of the structure. In that way I keep the memory allocation and data initialization as close to each other as possible, minimizing the chance of data being used in an uninitialized state.

Structures in C++
C++ addresses the problem of initialization of structures with constructors. With constructors you can ensure that all members of the structure or class are initialized when the structure is declared. For global data one problem remains. You still have to control the order in which the constructors happen if different structures are interdependent and a change in the order of the constructors during initialization could break the code.

For global data, the constructors are functions that are run before main. While this has some advantages, it can make the order-of-initialization problem worse. If multiple objects are constructed then the order of the constructors will be the order of declaration within one module, but the order across modules is not defined. Having an undefined order for start-up code is obviously not good, so this technique is really only useful for simple initializations.

Slow starters
Another initialization problem involves variables that may be modified many times before their value is valid, which can happen when values are averaged or filtered.

One of the system variables displays its value whenever it's changing. Each time the sensor reading is examined, the program compares it to the last value. If the difference is more than a threshold, the value displays and remains on the display so long as it's changing.

The first time the check is made is an exception, since no previous value exists to compare with; thus, the first value is treated as a nonchanging value. When we tested the system we found that the value was displayed briefly on start-up. A change was detected for the first few readings of the value, even though the voltage generated at the external sensor had not changed.

The problem was caused by a filter that was applied in software to the sensor signal. The sensor reading was initialized to zero on start-up and it took a number of readings before the filtered value accurately reflected the signal from the sensor. During this time the output of the filter was gradually changing from zero to the sensor reading, and that change was enough to trigger the display of the value.

The lesson here is that filtered or averaged values are often not accurate until later than you might think. Using the value for a PID (proportional, integral, derivative) control algorithm was not a problem, since any instability caused by the incorrect values vanished in a short period of time. The problem arose because software was making discrete decisions, such as turning on the display of the value, based on an analog input that was slow to reach its steady state. The fix was to recognize the first reading of the sensor and set the filtered output value to equal the sensor reading. This eliminates ramping up from zero to the sensor signal.

When is a reset not a reset
Recently I worked on a system in which we purposely kept the processor from resetting when the system restarted. When the user pressed the off button, the system went into a sleeping state until the user turned the device on again. Though the system's initialization code ran after the device was turned on, the reset line was not asserted, and so the start-up routines provided by the compiler, which run before main, weren't called. We could have also branched to the location of the reset vector, which would've been as effective as a processor reset from a software point of view, and would have avoided the problems I'm about to describe.

We avoided resetting the processor because we wanted to preserve some values through the restart. For all other values we simply wanted them to be initialized again. This restart policy hadn't been decided upon at the start of the project, so there were many places where globals were initialized in their declaration, with the assumption that this initialization would occur for every start-up sequence. This initialization was not the case, so we got caught out when the globals started life with the value left over from the previous run of the system.

To avoid further problems, we adopted a policy to give every module an initialization function. One of the duties of the initialization function is to set the value of any globals owned by that module. We restricted the initialization in the declaration for globals and file static values to the few values that we wanted to preserve through a restart.

Starting a relationship
An embedded systems designer should give careful thought to the relationship between stored values when the system is running because one value may be used to calculate another. A value might only be valid if a particular Boolean variable is true. There's also a set of relationships in place during the start-up sequence, and these rules dictate the order of initialization. Designers need to be mindful of these start-up relationships if they want their system to boot cleanly every time.

Niall Murphy has been writing software for user interfaces and medical systems for 10 years. He is the author of Front Panel: Designing Software for Embedded User Interfaces. Murphy's training and consulting business is based in Galway, Ireland. He welcomes feedback and can be reached at nmurphy@panelsoft.com. Reader feedback to this column can be found at www.panelsoft.com/murphyslaw.

References

  1. Murphy, Niall, "Assertiveness Training for Programmers," Murphy's Law, Embedded Systems Programming, April 2001, p. 53.
  2. Murphy, Niall, "Safe Memory Utilization," Embedded Systems Programming, April 2000, p. 110.
  3. Murphy, Niall, "Flushing Out Memory Leaks," Murphy's Law, Embedded Systems Programming, March 2002, p. 37.

Loading comments...