Favorite Tools: C++11 std::array
Many embedded software and firmware projects must be developed to high standards of reliability. To meet these reliability requirements, firmware project teams will consider many design tradeoffs. For example, an engineering team may avoid or outright ban the use of dynamic memory allocation, a feature typically accessed via the C library call "malloc" or the C++ allocator "new". When authoring software under such constraints, firmware engineers typically resort to static compile time allocation of all necessary arrays, data structures, and variables. The benefits of a static allocation strategy include:
- When using dynamic memory allocation, a long running system may "fragment" the memory pool (aka heap), resulting in unexpected and random memory allocation failures.
- The run-time requirements for allocating and de-allocating memory from the heap are non-deterministic, negatively impacting a product’s real-time response requirements.
- Static memory allocations allow for compile and link time enforcement of the target device’s RAM constraints.
- No overhead.
- Does not use the heap.
- Works with an initializer list.
- Knows its size (i.e. number of elements).
- It is a sequence container and provides an interface much like std::vector.
- Does not convert to a pointer unless you explicitly ask it to.
- Quoting Stroustrup: “In other words, it is very much like a built-in array without the problems…. The standard array's features makes it attractive for embedded systems programming (and similar constrained, performance-critical, or safety critical tasks).”
In case it wasn’t already obvious: I’m sold! Additionally, we should emphasize the fact that a C++11 std::array is fundamentally more type-safe than a standard C array.
To understand how we might use the std::array container, feel free to take a look at this demo project: FreeRTOSEsp32AccelLedStripMqttDemo and specifically this template based class: ColorMappedDataVisualizer.hpp. In the context of this post, here are the appropriate samples of code:
template<uint16_t NUM_PIXELS_, rmt_channel_t RMT_CHANNEL_, gpio_num_t GPIO_NUM_> class ColorMappedDataVisualizer <cut> std::array<led_color_t, NUM_PIXELS_> mDataPoints; std::array<led_color_t, NUM_PIXELS_> mLedStripBuf1; std::array<led_color_t, NUM_PIXELS_> mLedStripBuf2;
In this project, the ColorMappedDataVisualizer class handles visualization for an attached LED Strip. The number of “pixels” (aka LEDs) is configured via the template parameter NUM_PIXELS_ therefore the size of the LED strip is constrained at compile time, yet is still flexible allowing different builds for different product configurations. Additionally, all arrays and buffers necessary for servicing the LED strip are allocated entirely at compile time based on the NUM_PIXELS_ template parameter. Other open source classes providing similar services were using the heap to dynamically allocate the buffers necessary for the LED strip, which means a user of those classes may not immediately realize their software will fail when they increase the LED strip size. With the approach seen in the ColorMappedDataVisualizer class, the user will incur a build error when RAM usage has exceeded the target device resources. Early compile time or link time errors are preferred over later run time errors.
Other benefits include access to existing C++ standard library features and methods. An example in our demo class includes a use of std::rotate:
std::rotate(mDataPoints.rbegin(), mDataPoints.rbegin() + 1, mDataPoints.rend());
Using std::array also implies an easy transition path to using std::vector if the product requirements change and dynamic memory allocations are required instead of static allocations.
And finally, std::array is trivial to use with legacy C based libraries. See code in ColorMappedDataVisualizer such as:
mLedStrip.led_strip_buf_1 = mLedStripBuf1.data();
where a pointer to the raw underlying array is passed to a C library.
With all that being said: What is not to like? How have you used the C++11 std::array container in your embedded system software projects?
Previous post by Matthew Eshleman:
Favorite Tools: C++11 User-defined literals
Next post by Matthew Eshleman:
Tenderfoot: Introduction to Magic (Numbers that is...)
std::array is great and thanks for the post. This is slightly off-topic, but an unfortunate finding with a couple popular tools (Keil and Embedded Studio thus far that I've seen) is that while they now support C++ '11 semantics, they don't ship with/support the newer template classes such as std::array. The array header was completely missing when using the Keil v5 compiler with C++ '11 enabled and when I tried to use Segger's latest version of Embedded Studio, same problem, with no updates to their distributed libraries. I think Rowley may unfortunately have the same problem. Rowley and Embedded Studio are both gcc based so this was surprising. Still finding the time to configuring to use gcc externally for the Rowley/Segger solutions in order to get the latest libraries. I have completely abandoned Keil due to a variety of documented flaws and things like this.
Good for everyone to know, thanks! I've been lucky to work with almost all modern c++/gcc based projects, so haven't encountered that myself yet.
To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.
Registering will allow you to participate to the forums on ALL the related sites and give you access to all pdf downloads.