Journal Articles

CVu Journal Vol 11, #3 - Apr 1999 + Programming Topics
Browse in : All > Journals > CVu > 113 (22)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Compile Time Assertions in C

Author: Administrator

Date: 03 April 1999 13:15:30 +01:00 or Sat, 03 April 1999 13:15:30 +01:00

Summary: 

Body: 

C has a facility for checking dynamic assertions at run-time. It's inside <assert.h> and its called assert(). assert() is a macro, so why isn't it called ASSERT()? I don't know. Prior art no doubt. Anyway, assert() is a dynamic runtime feature, you can only use it inside functions.

/* not inside a function, won't compile :-( */
assert(sizeof(int) * CHAR_BIT >= 32);  

That's a pity because it would be nice if I could get the compiler to check things like this automatically at compile time. I've occasionally seen an attempt at a compile-time check like this...

#if sizeof(int) * CHAR_BIT < 32
#error People of Earth. Your attention please...
#endif

But this doesn't work. The C preprocessor is a glorified text reformatter: it knows practically nothing about C. However, there is a way to write this as a compile time assertion (and moving any error trap to an earlier phase is a Good Thing) { yourself }...

#define COMPILE_TIME_ASSERT(expr)   char constraint[expr]
COMPILE_TIME_ASSERT(sizeof(int) * CHAR_BIT >= 32);

What's is going on here? Well, the example preprocesses to...

char constraint[sizeof(int) * CHAR_BIT >= 32];

If the expression is true, (an int is at least 32 bits), the expression will have a value of one, and constraint will be an array of one char. If the assertion is false, (an int is less than 32 bits), the expression will have a value of zero, and constraint will be an empty array. That's illegal, and you'll get a compile time error. Viola, a compile time assertion ☺ You can use it inside and outside a function but you can't use it twice in the same function, as you end up with a duplicate definition of the variable called constraint. To solve that problem you could resort to some convoluted macro trickery...

#define COMPILE_TIME_ASSERT(expr)       char UNIQUE_NAME[expr]
#define UNIQUE_NAME                     MAKE_NAME(__LINE__)
#define MAKE_NAME(line)                 MAKE_NAME2(line)
#define MAKE_NAME2(line)                constraint_ ## line

But this is pretty horrible. Also, you will probably get warnings about unused variables. Let's take a step back for a moment and think about why it works at all. It's because you have to specify the size of an array as a compile time constant. The formal grammar of a direct-declarator tells you this.

  direct-declarator:
    identifier
    ( declarator )
    direct-declarator [ constant-expression opt ]
    direct-declarator ( parameter-type-list )
    direct-declarator ( identifier-list opt )

I just piggy backed on this, using the constrait that the value of the constant expression cannot (in this context) be zero. A natural question (to the curious) is what other parts of the formal grammer require a constant expression. Well, first off there is the value of an enumeration-constant

  enumerator:
    enumeration-constant
    enumeration-constant = constant-expression

However, I can't use this because there are no useful constraints in this context. Another one is the size of a bit-field.

  struct-declarator:
    declarator
    declarator opt : constant-expression

And reading the constraints of a bit field I see that if the width of a bit-field is zero the declaration cannot have a declarator. In other words this is legal

  struct x { unsigned int : 0; }

but this is not:

  struct x { unsigned int bf : 0; }

This suggests another way to create a compile time assertion

#define COMPILE_TIME_ASSERT(expr)   struct x { unsigned int bf : expr; }
COMPILE_TIME_ASSERT(sizeof(int) * CHAR_BIT >= 32);

Trying this we again get duplicate definitions, not of a variable this time, but of the type struct x. However we can fix this by creating an anonymous struct.

#define COMPILE_TIME_ASSERT(expr)   struct { unsigned int bf : expr; }

This works. However, now you'll probably get warnings about the unused untagged struct. There is one last bit of grammar that uses a constant-expression. The humble switch statement.

  labelled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

It's well known that you can't have two cases labels with the same constant. The following will not compile.

  switch (0)
  {
  case 0:
  case 0:;
  }

So, here's yet another way to create a compile time assertion. This time we don't create a dummy variable, or a dummy type, but a dummy statement. A dummy switch statement.

#define COMPILE_TIME_ASSERT(pred)       switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT(sizeof(int) * CHAR_BIT >= 32);

If pred evaluates to true (i.e., 1) then the case labels will be 0 and 1. Different; Ok. If pred evaluates to false (i.e., 0) then the case labels will be 0 and 0. The same; Compile time error. Viola. However, a switch statement cannot exist in the global scope. So the last piece of the puzzle is to put the compile time assertions inside a function.

#include <limits.h>
#define COMPILE_TIME_ASSERT(pred)       switch(0){case 0:case pred:;}
#define ASSERT_MIN_BITSIZE(type,size)        \
  COMPILE_TIME_ASSERT(sizeof(type) * CHAR_BIT >= size)
#define ASSERT_EXACT_BITSIZE(type,size)       \
  COMPILE_TIME_ASSERT(sizeof(type) * CHAR_BIT == size)
void compile_time_assertions(void)
{
  ASSERT_MIN_BITSIZE(char,  8);
  ASSERT_MIN_BITSIZE(int,  16);
  ASSERT_EXACT_BITSIZE(long, 32);
}

Editor's Comments

I have a couple of problems with the above. The biggest one is that anyone doing maintenance better be a pretty good C programmer. Anyone less than that is not going to be able to distinguish this kind of arcane code from the awful rubbish that represents the efforts of so many 'too clever for their own good' coders.

The second one is that you may have to throw the ISO compliance switch to shut down vendor provided extensions. That would be fine except that at least one well known vendors compiler will then generate thousands of warnings, errors etc for its own library code.

Thirdly it could be dependent on the version of the C standard you are using (and I have not checked, but it might fail if you are using your compiler in C++ mode).

Notes: 

More fields may be available via dynamicdata ..