C Tips and Tricks, Part II

Why you really should put const on the right side of your uint32_t

In my last installment of this series, I mentioned how C types “read” from right to left, and that const/volatile types are easier to read if you keep the const/volatiles on the right side of the types that they modify. I stopped short of actually recommending that you always do so as I wasn’t sure it really mattered.

However, I just ran into a real world reason why it’d be a good idea to get into the habit of always putting the volatile and const keywords on the right side of the type that they modify.

I was using a library that allowed you to define a macro to specify an important type. Internally, it had some code like this:

volatile SOME_MACRO_TYPE value;

At first glance, this looks just fine, and if I do:

#define SOME_MACRO_TYPE uint32_t

It works as you’d expect. But, if I do:

#define SOME_MACRO_TYPE char*

It doesn’t! The resulting declaration is:

volatile char* value;

The library expects value to be a volatile, but in this case, by specifying a pointer type, we are marking the thing value is pointing to as volatile, not value itself!

If we change the declaration of value to:

SOME_MACRO_TYPE volatile value;

Things work as expected in both cases.

C #include cycles and how to break them:

Errors caused by #include cycles in C can be very confusing, especially to programmers who are used to languages with sensible import systems. Sometimes the cycles can span 5 or 6 header files and can be a real nightmare to unravel. Here is an explanation of the problem and how to fix it.

Say we have the following 3 files: moduleA.h moduleB.h and somefile.c


// moduleA.h
#ifndef MODULEA_H
#define MODULEA_H

#include "moduleB.h"

typedef struct
{
  ...
} ModuleAType;

void SomeFunctionA(ModuleAType* a, ModuleBType* b);

#endif /* MODULEA_H */


// moduleB.h
#ifndef MODULEB_H
#define MODULEB_H

#include "moduleA.h"

typedef struct
{
  ModuleAType someField;  //   <----- this is the tricky bit
} ModuleBType;

void SomeFunctionB(ModuleAType* a, ModuleBType* b);

#endif /* MODULEB_H */


// somefile.c
#include "moduleA.h"

...

When we compile, we'll get an error that is something like: moduleB.h:8: error: expected specifier-qualifier-list before 'ModuleAType',  which in this case means that ModuleAType is not defined.

What!? But I include moduleA.h right there! Why doesn't it see it! Aren't my #ifndef XXXXX_H/#define XXXXX_H supposed to prevent these sorts of problems?

Actually, the #ifndef/#define's are part of the problem. What's happening is that somefile.c is including moduleA.h, which includes moduleB.h, but when moduleB.h includes moduleA.h the #ifndef/#define's prevent the contents from being unrolled, since we've already included moduleA.h (and this is good, if we didn't we'd end up with an infinite include loop)
Once all the includes are unrolled, somefile.c will look like this:


// somefile_preprocessed.c
typedef struct
{
  ModuleAType someField;  //   <----- this is the tricky bit
} ModuleBType;

void SomeFunctionB(ModuleAType* a, ModuleBType* b);

typedef struct
{
  ...
} ModuleAType;

void SomeFunctionA(ModuleAType* a, ModuleBType* b);

...

Now the error is apparent. ModuleAType is definitely being referenced before it is defined.

Normally, when we run into a problem like this, the easiest solution is to just add structure prototypes for ModuleBType in moduleA.h, and for ModuleAType in moduleB.h.
However, that won't work in this case. It will work for moduleB.h, but not moduleA.h. ModuleBType has a full ModuleAType (not just a pointer) as a member. A structure prototype by itself won't work in this case.

Steps to fix:

  • The error shows up in one of your .h files, but take note of the .c file that just failed to compile. This is where you want to start.
  • Remember that #include effectively just pastes in the contents of the included file, replacing the #include.
  • If you have trouble mentally unrolling all your nested #include's use your compilers "preprocess only" flag (-E for gcc) to output the preprocessed version of your offending C file
  • Once you've found where and why the cycle is causing problems, you can try to untangle it as best fits your needs. There are a lot of solutions that work for various cases, but here is a solution that will always work:
    • move the offending structures out of the header files that contain prototypes and put them in their own header file(s).
    • replace them with struct prototypes and typedefs (Do _not_ include the new header here)
    • #include the new header file with the structure definitions in the .c files that need it.

So in our example, moduleB.h would now look something like this:


// moduleB_fixed.h
#ifndef MODULEB_H
#define MODULEB_H

#include "moduleA.h"

struct ModuleBType_s;
typedef struct ModuleBType_s ModuleBType;
void SomeFunctionB(ModuleAType* a, ModuleBType* b);

#endif /* MODULEB_H */

and there would be a moduleB_types.h that would look something like this:


// moduleB_types.h
#ifndef MODULEB_TYPES_H
#define MODULEB_TYPES_H

#include "moduleA.h"

struct ModuleBType_s
{
  ModuleAType someField;
};

#endif /* MODULEB_TYPES_H */

and somefile.c would look like:


// somefile_fixed.c
#include "moduleA.h"
#include "moduleB_types.h"

...

Problem solved!

Conversation
  • Jordan Schaenzle says:

    Nice one Job. I have been caught in #include hell a few too many times in the past. This will help!

  • Comments are closed.