How to Catch Errors in Embedded Development at Compile Time?

Have you ever thought about how some simple syntax rules in C can lead to clever methods?
For example, the length of an array in C cannot be negative; of course, it makes no sense for an array length to be negative.
For instance, int arr[1]; is correct, while int arr[-1]; is incorrect.
This rule is simple and easy to understand, and it doesn’t attract much attention.
However, I claim that this syntax rule can be used for C’s assert error checking. Do you believe it?
Excellent programmers generally try every possible way to intercept errors in their programs as early as possible, at compile time and pre-compile time, rather than letting them leak into the runtime phase, especially not in products that reach users.
For intercepting errors at the pre-compile stage, it’s relatively simple. This can be done through pre-compile directives like #if and #error. For example,
1. Priority checks in FreeRTOS require users to define the priority greater than or equal to 1; otherwise, an error is reported.
#if configMAX_PRIORITIES < 1      #error configMAX_PRIORITIES must be defined to be greater than or equal to 1.#endif
This is because if the program encounters configMAX_PRIORITIES < 1, it may lead to more serious errors.
Thus, intercepting this error at the pre-compile stage is very effective.
2. The runtime state retrieval function in FreeRTOS must rely on the definition of configUSE_TRACE_FACILITY.
void vTaskGetRunTimeStats( char *pcWriteBuffer ){TaskStatus_t *pxTaskStatusArray;UBaseType_t uxArraySize, x;uint32_t ulTotalTime, ulStatsAsPercentage;#if( configUSE_TRACE_FACILITY != 1 ){    #error configUSE_TRACE_FACILITY must also be set to 1 in FreeRTOSConfig.h to use vTaskGetRunTimeStats().}#endif

Because this function heavily relies on the implementation of other functions or resource definitions, configUSE_TRACE_FACILITY manages these.

3. Version checks are necessary if you design a more generic function that can be used across many projects. To facilitate management and iterative updates, you need to define a version for this functional module.Different versions may have differences and could be incompatible, at which point you can use pre-compile directives to check for compatibility.
#if (XXX_VER < 0x201)    #error "This version of XXX is too low!"#endif
Checking for errors through pre-compile directives is quite limited because pre-compile directives can only check immediate values and some logical relationships.
Thus, we also need to consider compile-time error checking, which checks the results produced at compile time.
For example, checks on the length of unsigned long or void* must be done at compile time.
Since the lengths of unsigned long and void* may vary across different chip architectures, if your program relies on the length of this type, you must check that its length is as you designed.
For example, using #if sizeof(unsigned long) == 4 is incorrect because pre-compile cannot compute sizeof(unsigned long).
So is there a way to ensure that sizeof(unsigned long) equals 4 without error?
Perhaps you might think of something called assert, for example, defined in FreeRTOS:
#define configASSERT(x)   if((x)==0) { taskDISABLE_INTERRUPTS(); for( ;; ); }
This can be used like this:configASSERT(sizeof(unsigned long) == 4). If sizeof(unsigned long) == 4, the program is “silent” as if nothing happened. However, if sizeof(unsigned long) == 8, the program will enter for( ;; ); and cannot escape.
However, this approach is at runtime; it must be done after the program is integrated and running in an actual environment to discover such errors, which can be costly to debug.
So is there a way to catch this at compile time?
The answer is yes; C++11 and C11 support this kind of assert called STATIC_ASSERT, which requires including the header file assert.h.
But what if I haven’t used C11 and still want to implement this STATIC_ASSERT? Is there a way to do it myself?
The answer is also yes, you need to think of a trick, for example, starting from the length of an array.
Definitions like int arr[1]; are fine, but int arr[-1]; will cause a compiler error. We can leverage this to our advantage by replacing the array length with a check condition COND; if COND is TRUE, no error is reported; if FALSE, an error is reported.
After some repeated attempts, we might achieve something like this:
int arr[(!!(COND))*2-1];
Here, (!!(COND))*2-1 has no redundant symbols; a brief explanation:
1. (COND) adds a layer of parentheses to prevent any unknown precedence issues if COND is a complex expression.
2. (!!(COND)) uses two exclamation marks, which means logical negation twice. You might find it strange; !!TRUE is still TRUE, and !!FALSE is still FALSE. However, you need to think carefully about the definitions of TRUE and FALSE. In C, FALSE is 0, while any non-zero value is TRUE. This non-zero value has a lot of room for interpretation; for example, the integer 100 is also TRUE, but !!100 will become 1, and (!!(COND)) is designed to ensure the result is either 0 or 1, not any other number.

3. With the explanation in point 2, it’s easy to understand that (!!(COND))*2-1 guarantees that the result is either 1 or -1, without other values. Because our goal is to achieve int arr[1]; or int arr[-1];.

Using an array definition in the program seems less user-friendly; we can replace it with:
typedef int arr[(!!(COND))*2-1];
To also see a bit more error information, we can define it as a macro with an additional parameter, which would look like this:
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
On this basis, we can finalize others like this:
// token pasting madness:#define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)#define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)#define COMPILE_TIME_ASSERT(X)    COMPILE_TIME_ASSERT2(X,__LINE__)
Next, we can check if sizeof(unsigned long) is equal to 4 like this:
STATIC_ASSERT(sizeof(unsigned long) == 4,unsigned_long_size_is_not_4_error);
That’s all the content. Isn’t it interesting? Follow the public account “Embedded Software Practitioners“, and I will share more knowledge and skills with you.
References: Static assert in C(https://stackoverflow.com/questions/3385515/static-assert-in-c)
If this article has helped you, please give it a thumbs up πŸ‘πŸ» and let me know what you think. Thank you!

If you like my articles, please follow, and share, like and comment. This is a great encouragement for me!

Previous Recommendations

“Complete Guide to Embedded Linux Drivers”

Recommended: A useful static code scanning tool for embedded systems!

Embedded systems combined with iFlytek’s cognitive large model, awesome!

Reply 1024 in the public account chat interface to get embedded resources.

Leave a Comment