MISRA-C friendly C99 static assert macro

January 14, 2023

…and a little extra!

⚠️ If you already know what I’m talking about and you just want the macro, you can directly jump to the conclusions.

Oftentimes, especially in embedded programming, there are some checks that are better run at compile time rather than at runtime. Let’s say that you have a codebase (for example a library) that has some functionality that relies on the platform’s unsigned int to be exactly 4 bytes, or a long to be exactly 8, or the char type to be signed, and so on. There could be many comments written in the code about these constraints, but in the end who’s stopping you (or someone else) from using this code?

Consider this: let’s say that you have a struct that needs to be exactly of a certain, specified size for some library/application:

  • Example 1:
typedef struct // __attribute__((packed))
{
    short a;
    char b;

} PackedOrNot_t;

/*
 * Do we need the packed attribute?
 * If so, how do we enforce it?
 */

In this case the struct’s sizeof size can be either 3 or 4 bytes, depending on if we use a packed attribute or not. How can we make sure that such an error never slips? What if we have other people working with us at the same project, or if we need to hand over the project/library altogether?

Or another example: let’s assume that we have a struct that is 1:1 representative of some specific, real-world data. Let’s say that it needs to be sent via an interface that requires it to be a multiple of 8 bytes in size (e.g. via CAN). How can you be programmatically secure that adding elements to our struct results in it being exactly 8-bytes aligned? We might add some comments explaining how to pad the structure with extra reserved bytes but we’re always a slip away from a potential source of nasty runtime bugs.

  • Example 2:
typedef struct
{
    char someData;
    char someMoreData;
    ...
    /* Assume this structure definition is very long,
     * possibly even with other structs inside of it.
     */

} EightBytesMultiple_t;

/*
 * ..are we sure that it's always going 8-bytes*n long?
 */

One last example: before C99, integer division behavior was implementation dependent (rounding can go toward zero or toward negative infinity). From C99 onwards, integer division is well defined as it performs truncation towards zero. If some of your logic depends on this behavior being well defined, how do you enforce at compile time?

  • Example 3:
...
// Before C99 (for example in ANSI-C) the result could
// be either -2 or -3 depending on the implementation!
int result = -10 / 4;

Luckily, C11 features the _Static_assert predicate, as can be seen here. However, sometimes we are restricted to use older C standards, such as C99, especially for embedded/automotive use. So, is there a way around this conundrum in older C versions?

No preprocessor was harmed in the writing of this article

Actually yes, we can get around this by taking advantage of clang’s or gcc’s preprocessor/compiler. What we could think of doing, is something kinda like this:

typedef char __something[(expression) ? (+1) : (-1)];

Here, the compiler will evaluate whatever expression that can be statically resolved inside the parentheses and substitute +1 if the expression evaluates to true or -1 otherwise. Since arrays can’t be declared with a negative size, we get a compiler error in case of a negative expression result; or in other words, an assertion failure. Hooray! Let’s expand on this; let’s have a macro do all of the typing for us:

#define STATIC_ASSERT(expr) typedef char __something[(expression) ? (+1) : (-1)];

The first obvious problem is that we are typedef-ing a new variable type always with the same name (we’ll come back to this later…); also, the __something placeholder that we use might be used already for something else. Ok, so we’ll fix those two potential problems using macro concatenation and a little help from __COUNTER__:

#define GLUE(a, b) a##b
#define EVAL(expr, name) typedef char GLUE(__assert_test_t_, name)[(expr) ? (+1) : (-1)]
#define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)

Now, the __COUNTER__ is a special compiler extension macro that gets incremented every time that it’s used. So, for example, the preprocessing of:

...
STATIC_ASSERT(sizeof(int) == 4)
STATIC_ASSERT(sizeof(long) == 8)

Will result in something like:

...
typedef char __assert_test_t_0[(sizeof(int) == 4) ? (+1) : (-1)];
typedef char __assert_test_t_1[(sizeof(long) == 8) ? (+1) : (-1)];

These two assertions will correctly fail (give a compiler error) whenever they need to. So that’s it, Right?

MISRA comes in and ruins the fun

Remember when I said that two typedefs can’t have the same name? Actually, the compiler doesn’t mind; but MISRA-C does. So what is it? It’s a set of software development guidelines for the C programming language, aimed to facilitate code safety, security, portability and reliability in the context of embedded systems. It was originally written for the automotive embedded software industry, but nowadays it’s also used in other sectors, such as defense, telecommunications, medical devices, etc. If you have ever worked as an embedded C developer in one of those fields you’ll know what I’m talking about.

The macro in its original form re-defines a typedef multiple times, violating:

  • MISRA-C:2012 rule 5.7: A tag name shall be a unique identifier.

The second version with __COUNTER__ solves this constraint. But, we have a new violation:

  • MISRA-C:2012 Rule 2.3: A project should not contain unused type declarations.

This is because for each STATIC_ASSERT(...) we are defining a new type alias by the name __assert_test_t_n, but we are never actually using it in the code. Can we circumvent this too? Of course! We just need to actually use the aliased type. Watch this:

#define EVAL(expr, name)                                             \
    typedef char GLUE(__assert_test_t_, name)[(expr) ? (+1) : (-1)]; \
    static GLUE(__assert_test_t_, name) GLUE(__assert_test_var_, name)
#define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)

What we are doing is basically declaring a static variable named __assert_test_var_n for each __assert_test_t_n typedef declaration. This assures compliancy with the latter rule.

However, chances are, that if we check our codebase using MISRA-C rules there is a very high possibility that we also use all of the warning flags available to us (such as -Wall and -Wextra). In this case, we’ll get a bunch of warnings telling us about our unused variable declarations; using gcc -Wall -Wextra:

example.c:6:46: warning: ‘__assert_test_var_0defined but not used [-Wunused-variable]
    6 |     static GLUE(__assert_test_t_, name) GLUE(__assert_test_var_, name)
      |                                              ^~~~~~~~~~~~~~~~~~
example.c:3:20: note: in definition of macroGLUE    3 | #define GLUE(a, b) a##b
      |                    ^
example.c:7:29: note: in expansion of macroEVAL    7 | #define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)
      |                             ^~~~
example.c:18:1: note: in expansion of macroSTATIC_ASSERT   18 | STATIC_ASSERT(sizeof(int) == 4);
      | ^~~~~~~~~~~~~

And using clang -Wall -Wextra:

example.c:18:1: warning: unused variable '__assert_test_var_0' [-Wunused-variable]
STATIC_ASSERT(sizeof(int) == 4);
^
example.c:7:29: note: expanded from macro 'STATIC_ASSERT'
#define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)
                            ^
example.c:6:41: note: expanded from macro 'EVAL'
    static GLUE(__assert_test_t_, name) GLUE(__assert_test_var_, name)
                                        ^
example.c:3:20: note: expanded from macro 'GLUE'
#define GLUE(a, b) a##b
                   ^
<scratch space>:49:1: note: expanded from here
__assert_test_var_0
^
1 warning generated.

This can be hastily fixed though, using the __attribute__((unused)) compiler extension in our macro. By adding this:

#define EVAL(expr, name)                                             \
    typedef char GLUE(__assert_test_t_, name)[(expr) ? (+1) : (-1)]; \
    static GLUE(__assert_test_t_, name) GLUE(__assert_test_var_, name) __attribute__((unused))
#define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)

We get zero warnings with both gcc -Wall -Wextra and clang -Wall -Wextra. While using the latter, if we compile with -g to include the debug symbols as well, we can also see using objdump that the assertion’s type alias symbols and static variables definitions don’t even get included in the final binary object, and thus not even using an extra bit of space (tested with gcc 11.4.0 and clang 14.0.0).

About that MISRA…

Actually, you might have noticed that this static assertion implementation clearly violates one of MISRA’s rules:

  • MISRA-C:2012 rule 20.10: The # and ## preprocessor operators should not be used.

However, I would like to point out that this rule is only advisory and its analysis is decidable. Moreover, the rationale behind this rule argues that:

Rule 20.10 Rationale:

The order of evaluation associated with multiple #, multiple ## or a mix of # and ## preprocessor operators is unspecified. In some cases it is therefore not possible to predict the result of macro expansion.

The use of the ## operator can result in code that is obscure.

In our case though, the evaluation order is clearly well defined and leaves no room for interpretation; so I think that it’s a valid exception to the rule, and that the benefits of having a static assertion predicate in C99 is totally worth it.

Conclusions

Here is the macro in its final form:

#define GLUE(a, b) a##b
#define EVAL(expr, name)                                             \
    typedef char GLUE(__assert_test_t_, name)[(expr) ? (+1) : (-1)]; \
    static GLUE(__assert_test_t_, name) GLUE(__assert_test_var_, name) __attribute__((unused))
#define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)

With this predicate we would get a compilation error whenever the expression fails; the problems described above would be managed as such:

  • Ex. 0 (platform specific type checks):
    // Are the platform's types of the required size?
    STATIC_ASSERT(sizeof(int) == 4);
    STATIC_ASSERT(sizeof(long long) == 8);
    ...
    // Is the "char" type signed?
    #include <limits.h>
    STATIC_ASSERT(CHAR_MIN < 0);
    
  • Ex. 1:
    // Did you forget to pack your struct?
    STATIC_ASSERT(sizeof(PackedOrNot_t) == 3);
    
  • Ex. 2:
    // Did you forget some extra padding bytes in your struct definition?
    STATIC_ASSERT(sizeof(EightBytesMultiple_t) % 8 == 0);
    
  • Ex. 3:
    // Does integer division with negative values correctly round towards zero?
    STATIC_ASSERT(-10 / 4 == -2);
    

Something extra

I just wanted to add and extra goodie: let’s say that you have a very long and nested struct definition in your code:

typedef struct
{
    int a;
    short b;
    unsigned long c[10];
    SomeOtherStruct_t d;
    ...

} VeryLongStruct_t;

What if you needed to get its sizeof without doing all of the calculation by hand, or going in debug mode? Well this macro comes in handy:

#define SIZEOF_WARN(type) char (*__var)[sizeof(type)] = 1;

When compiled, this macro will give a compiler warning with the correct sizeof in the warning’s output. Example use:

SIZEOF_WARN(VeryLongStruct_t)

Compile with the usual -Wall -Wextra flags and voila:

example.c:9:57: warning: initialization ofchar (*)[246]’ fromintmakes pointer from integer without a cast [-Wint-conversion]
    9 | #define SIZEOF_WARN(type) char (*__var)[sizeof(type)] = 1;
      |                                                         ^
example.c:20:1: note: in expansion of macroSIZEOF_WARN   20 | SIZEOF_WARN(VeryLongStruct_t)
      | ^~~~~~~~~~~

Your VeryLongStruct_t has a sizeof value of 246.