One of my favorite software development “tricks” is making frequent use of any “static assert” feature provided by the compiler. This trick’s primary benefit is in moving certain error conditions to compile-time rather than run-time. Embedded software engineers would be wise to sprinkle a these asserts in their projects. The benefits include:
- Save Time
- An error detected while compiling saves an engineer time versus trying to debug or detect the same issue at run-time. Time is money!
- Document Assumptions
- Each static assert communicates additional information to future engineers that inherit the project.
- Code Maintenance
- Helps prevent a future engineer from missing related data or associations that the compiler would not normally enforce.
- Portability
- Use a static assert to ensure data structures are packed in the same manner, even if the project changes compilers.
Additional examples and explanations may be found here: http://stackoverflow.com/a/1647921/49956
How to
Before proceeding, let us first note how to create a static assert template or macro. Key point to remember: a static assert is only able to evaluate data or data structures that are defined at compile time.
For pre-C++11 code, we must create or acquire a custom template such as the example shown in this answer on Stack Overflow: http://stackoverflow.com/a/6765840/49956
For C code, the exact approach to create a static assert macro might vary based on the compiler, but the following site lists various approaches: http://www.pixelbeat.org/programming/gcc/static_assert.html.
When developing with C++11 or newer, simply use the officially defined static_assert(), see here for details: cppreference.com.
Now that we have established a handful of approaches to define a static assert, let us dive into an example.
Examples
In this example, a module provides various methods using an enumerated type as a common input parameter. Internally to the module is a static lookup array (or table) with additional data relevant to each enumerated value. As a standard optimization the module uses the enum value itself as an index into the internal table. The author is worried about maintenance issues and wants to ensure that new enumerated values will be properly added to the internal look up table. How? Here is an example showing one approach.
public header: enum SwitchInputs { INPUT_1, INPUT_2, INPUT_COUNT }; Internal module code(C++11): static uint32_t* m_InputAddr[] = { (uint32_t*)0xA0000000, (uint32_t*)0xA0000010 }; static_assert(sizeof(m_InputAddr)/sizeof(m_InputAddr[0]) == INPUT_COUNT, "internal lookup table size does not match enum count!");
With that static assert in place, the author can now deliver the module with high confidence that a future software engineer will quickly discover that adding another value to the enum is not enough to expand the functionality of the module. The compiler will issue an error and the project will not build, giving the new engineer a clear signal that additional effort is needed! As long as the number of elements in the m_InputAddr[] array matches the enum count, the code compiles with no errors.
More Ideas
With an example behind us here are more ideas for using static asserts in an embedded software project.
Hardware Register Definitions
This is a very common embedded software concern. For example, a microcontroller may have 3 UART IP blocks, each with the exact same hardware registers located at different internal memory addresses. An engineer could define a data structure representing a single UART and simply allocate the structure at each UART’s unique address.
To verify that the compiler created the expected data structure, use static asserts checking the sizeof() the struct and use the offsetof() macro to verify internal members of the struct are located as defined in the device datasheet. Of course, know your compiler: many (if not all) provide for a “packed” pragma or other syntax to force the compiler to pack a data structure, avoiding compiler defined padding between structure members.
Packet Size Checks
Many IoT devices transmit and receive binary packets. An engineer may represent these packets with a data structure, e.g. struct Packet. The code may then transmit and receive packets directly to and from allocated data structures. But how do we know the data structure correctly represents the data packet?
Use a static assert on the sizeof(Packet) to confirm the compiler’s output matches the size of the externally documented structure. Then, consider using the offsetof macro to verify exact locations of one or two members of the struct. Again, this verifies that the compiler’s generated addresses for the data structure match the documented packet structure.
Static Buffer re-use
Memory is often at a premium in an embedded system. Consider two modules that are known to always operate at non-overlapping time periods. The design then requires these modules to use a common static RAM buffer in order to save memory. To prevent future maintenance mistakes, each module’s author should add an internal static assert verifying that the shared buffer meets requirements. Again, this is a great protection against future mistakes by engineers maintaining the code.
Memory Alignment
Some hardware IP cores expect memory to be aligned, sometimes to an extreme, such as a 1MB boundary. Make sure the compiler is placing buffers or data structure members as expected by using a static assert to confirm memory alignment.
Versions
Use a static assert to verify that an external library meets some minimum version requirement. This assumes the library provides its version via a static compile time mechanism.
Downside?
Is there a downside to using static asserts? Not really. Perhaps the only “downside” for me: with C++11’s formally defined static_assert(), I tend to always prefer C++11 (or newer) code bases.
How have you used static asserts in your embedded software and firmware projects?
Could your team benefit from expert advice on coding and related best practices? If so, check out our best practices advisor service or our code review service for more details.
2 comments