C Code to avoid

Fri, 17 Dec 2010 16:33:11 +0100
rants

One of the bad aspects of the the C Programing language is that it silently allows many bad C programs. Together with the widespread use of an architecture that is very bad at catching errors (i386), this sometimes leads to common idioms that are only working accidentally. This is bad as they often break on other architectures and can break with every new optimizations or new feature the compiler adds.

Take for example a look at the code there (I tried to leave a comment there but did not succeed):

If you see something like this:

     char buffer[1000];
     struct thingy *header;
     header = (struct thingy *)buffer;

then it is time to run. I hope you do not depend on this software, because it is a pure accident if this is doing anything at all.

While you can cast a char * to a struct, that is only allowed if that memory actually was this struct (or one compatible, like a struct with the same initial part and you are only accessing that part).

In this case it is obviously not (it's just an array of char), so you might see bus errors or random values if the compiler does no optimizations and you are on a architecture where alignment matters. Or the compiler might optimize it to whatever it wants to, because the C compiler is allowed to do everything with code like that.

The next problem is the one that post was about: You are not allowed to access an array after its end. Something like

 struct thingy {
     int magic;
     char data[4];
 }

means you may only access the first 4 bytes of data. If you access more than that it may work now on your machine, but it can stop tomorrow with the next revision of the compiler or on another machine.

If you have a struct with a variable length data block, then you can use the new C99 feature of char data[] or the old gcc extension of char data[0]. Or you can use unions. (Or in some case use the behavior of structs with the same initial parts).

If you use C code with undefined semantics then every new compiler might break it with some optimization. There is often the tempting option of just using a slightly different code that currently works. But in the not too distant future the compiler (or even some processor) might again get some new optimizations and the code will break again. Fixing it properly might be harder but it's less likely it will fail to compile again and it also reduces the chances that it will not fail to compile but simply do something you did not expect.