…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 typedef
s 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_0’ defined 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 macro ‘GLUE’
3 | #define GLUE(a, b) a##b
| ^
example.c:7:29: note: in expansion of macro ‘EVAL’
7 | #define STATIC_ASSERT(expr) EVAL(expr, __COUNTER__)
| ^~~~
example.c:18:1: note: in expansion of macro ‘STATIC_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 of ‘char (*)[246]’ from ‘int’ makes 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 macro ‘SIZEOF_WARN’
20 | SIZEOF_WARN(VeryLongStruct_t)
| ^~~~~~~~~~~
Your VeryLongStruct_t
has a sizeof
value of 246.