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 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
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.