Search code examples
crefactoringassertstatic-assert

Ways to ASSERT expressions at build time in C


I'm tidying up some older code that uses 'magic numbers' all over the place to set hardware registers, and I would like to use constants instead of these numbers to make the code somewhat more expressive (in fact they will map to the names/values used to document the registers).

However, I'm concerned that with the volume of changes I might break the magic numbers. Here is a simplified example (the register set is more complex):

const short mode0 = 0;
const short mode1 = 1;
const short mode2 = 2;

const short state0 = 0;
const short state1 = 4;
const short state2 = 8;

so instead of :

set_register(5);

we have:

set_register(state1|mode1);

What I'm looking for is a build time version of:

ASSERT(5==(state1|mode1));

Update

@Christian, thanks for the quick response, I'm interested on a C / non-boost environment answer too because this is driver/kernel code.


Solution

  • NEW ANSWER :

    In my original answer (below), I had to have two different macros to support assertions in a function scope and at the global scope. I wondered if it was possible to come up with a single solution that would work in both scopes.

    I was able to find a solution that worked for Visual Studio and Comeau compilers using extern character arrays. But I was able to find a more complex solution that works for GCC. But GCC's solution doesn't work for Visual Studio. :( But adding a '#ifdef __ GNUC __', it's easy to choose the right set of macros for a given compiler.

    Solution:

    #ifdef __GNUC__
    #define STATIC_ASSERT_HELPER(expr, msg) \
        (!!sizeof \ (struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; }))
    #define STATIC_ASSERT(expr, msg) \
        extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)]
    #else
        #define STATIC_ASSERT(expr, msg)   \
        extern char STATIC_ASSERTION__##msg[1]; \
        extern char STATIC_ASSERTION__##msg[(expr)?1:2]
    #endif /* #ifdef __GNUC__ */
    

    Here are the error messages reported for STATIC_ASSERT(1==1, test_message); at line 22 of test.c:

    GCC:

    line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message'
    

    Visual Studio:

    test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts
        test.c(22) : see declaration of 'STATIC_ASSERTION__test_message'
    

    Comeau:

    line 22: error: declaration is incompatible with
            "char STATIC_ASSERTION__test_message[1]" (declared at line 22)
    

     
     

    ORIGINAL ANSWER :

    I do something very similar to what Checkers does. But I include a message that'll show up in many compilers:

    #define STATIC_ASSERT(expr, msg)               \
    {                                              \
        char STATIC_ASSERTION__##msg[(expr)?1:-1]; \
        (void)STATIC_ASSERTION__##msg[0];          \
    }
    

    And for doing something at the global scope (outside a function) use this:

    #define GLOBAL_STATIC_ASSERT(expr, msg)   \
      extern char STATIC_ASSERTION__##msg[1]; \
      extern char STATIC_ASSERTION__##msg[(expr)?1:2]