Saturday, January 08, 2005

Initialize your structures, it may save your back

Some PalmOS API functions require pointers to structures. Usually, this is a standard operation-until APIs start calculating and returning junk.
Lets take the following piece of code as an example. It is a time limiter that lets a beta expire after a defined day, or rather on a fixed date.
DateTimeType date;
date.day=2;
date.month=2;
date.year=2005;
if(TimGetSeconds()>TimDateTimeToSeconds(&date))
{
FrmCustomAlert(1000,"Beta Period expired!!\n", " "," ");
return 222;
}

Looks perfect-doesn't it? Common sense finds no error. In tests, the routine didn't work well. The program did not expire at the date specified, but rather at a random date a few years later. After rewriting the code for a few times, the structure was nulled out. And alas, the code worked well(see below)
DateTimeType date;
date.minute=0;//All Values must be defined!!!
date.second=0;
date.hour=0;
date.day=2;
date.month=2;
date.year=2005;
if(TimGetSeconds()>TimDateTimeToSeconds(&date))
{
FrmCustomAlert(1000,"Beta Period expired!!\n", " "," ");
return 222;
}

As we all know, C compilers do not initialize local variables while they are declared. Thus, random values were contained in the hour, minute and second fields, each of which was defined as a UInt16(I don't know why->waste of memory without apparent bonus in simplicity). Thus, values of up to 2^16 could be contained. The API did not check for the sanity of the input, and thus the displacement occurred.
Discussing the matter with the developer community, three possibilities for nulling out structures. The first one was used in the example above. Altough it works well and makes a nice, fast fix, most programmers will prefer a tidyer way of duking it out with the structure.
A more advanced way of programming would be the use of structure constants. ANSI C allows the programmer to preload structures with definite values. You just use a list of elements, like shown in the example below:
struct{
int a;
int b;
int c;}foo;
foo={ValueA, ValueB, ValueC};

As you see, each structure element can have a value assigned-if the types match. If there are less values than struct elements, the ones defined first get values, and the rest stays undefined.
However, this solution still has a disadvantage. Every type of struct needs a different list.
The ultimate solution is the use of the MemSet function. If you aren't familiar with the call, look it up here.
If you now have a structure foo and want to zero it out, all you need is a call to
MemSet(&foo,sizeof(foo),0);
This can be packed into a parameterized macro if you plan to use it a lot. Use the preprocessor, you paid for it after all!

UPDATE-when zeroing out structures, think about what the caller expects to find there... . You an provoke a fatal alert easily.

6 Comments:

Blogger Ben Combee said...

Actually, that structure assignment syntax isn't in ISO C or ISO C++. You can initialize a structure using that syntax, but you can't assign a structure like that after it's been defined.

foo a = { 1, 2, 3 };

would work, but

foo a;
a = { 1, 2, 3 };

would not.

I think GCC does support the assignment syntax, but that's an extension to the C language. They added the idea of a structure literal, and effectively you've declared an anonymous structure with those values which is being assigned to the structure you've already declared.

9:56 PM  
Blogger Jonathan Hays said...

In addition, if you're compiling under the C++ compiler, you actually can create constructors for structs. While this doesn't help for system structures that are defined for you, it's not a bad practice for your own structs that you define.
Here's a rudimentary example:

//Definition...
struct foo
{
int a;
char b;
foo()
{
a = 0;
b = 0;
}
};

//Usage...
foo thing;
thing.a = 7;

1:03 AM  
Blogger Tom Frauenhofer said...

Take it one step further - in C define routines to allocate and deallocate your structure. For example, in your header file, have the following:

typedef struct {
UInt16 a;
Boolean b;
} FooType;

FooType* FooNew();
void FooFree(FooType* ptr);

Define FooNew() and FooFree() to whatever initialization logic you need. The best thing about this is that you can initialize your structure to whatever you want (MemSet is a little too broad brush sometimes ;-) ).

(BTW, This is the C equivalent of adding C++ constructors/destructors. Destructors/Deallocators are very useful, especially if you have deep structures that contain pointers to other memory you may have allocated - remember, memory cleanup is just as important as memory initialization!)

7:48 PM  
Blogger Tam Hanna said...

Hi all,
these posts were very interesting, I think that you all shall become my co-authors;-P...
Keep up the good work
Tam Hanna

8:20 PM  
Anonymous chetan.s said...

I faced a similar issue while working with one of the API library. In my case situation is was worse, the API used many interleaved structure pointers.
I used to get seg fault because of some uninitialized members.
I think in such cases it is better to declare the super structure (if u like) variable as static in the user code (client of the library) which will take care of initializing the members to the default initial value.

10:17 PM  
Anonymous Anonymous said...

Nice article but it would've saved me 4 hours of work if the syntax on memset was actually correct as listed here...unfortunately the size_t for a struct is also an int...so the damn compiler never complains!!!
-Frustrated engineer

5:29 AM  

Post a Comment

<< Home