Often we hear embedded software engineers avoiding the usage of C++ because of being scared by potential performance hit or code size explosions. Even though some features of C++ can have significant impact on the performance and code size, it would be a mistake to exclude the usage of the language completely because of this.
In this article I want to show a few additions to the C++11 and C++14 standards which can improve the readability of your code but won’t affect the performance thus can be used even with the smallest Cortex-M0+ core.
ARM Compiler 5 supports C++11 whereas ARM Compiler 6 supports both C++11 and the most recent C++14 (refer to documentation for details). If not specified, ARM Compiler 6 assumes C++03 as a standard so you need to use the command line options --std=c++11 or --std=c++14 to be able to use the newer standards. If you want to enforce the conformance to a specific standard you can use the command line option --pedantic-errors: armclang would generate an error in case you are using extension or features of the standard.
The constexpr keyword has been introduced with C++11 but C++14 removed a few constraints making this functionality even more powerful. When a function is declared as constexpr, the compiler will know that the result of that function can be evaluated at compile time and it can be used accordingly.
Let’s assume we want to create a static array based on the number of bits set in a word; with C++03 we would have written something similar to the following code:
const int my_word = 0xFEF1; // bit mask int *my_array; int number_of_bits(int word) { int count=0; while(word) { count += word & 0x1; word >>= 1; } return count; } ... my_array = (int*)malloc(sizeof(int)*number_of_bits(my_word)); ...
With C++14 is possible to calculate this in a function and the result is available at compile time. The code can be transformed as:
const int my_word = 0xFEF1; // bit mask constexpr int number_of_bits(int word) { int count=0; while(word) { count += word & 0x1; word >>= 1; } return count; } int my_array[number_of_bits(word)];
Because the function is evaluated at compile time, the compiler can instantiate the array on the stack saving the call to malloc() at run-time: readability and performance have been improved at the same time!
Often in our applications, we need to use bit masks or perform bit operations: How many times did we write code similar to the following?
if (x & 0x20) { // 0010 0000 ... }
What does 0x20 mean in this code? For an expert programmer this is clearly checking if the sixth LSB bit of x is set but it might be getting trickier with more complex bit masks. C++14 makes this even more clear. In the latest version of the standard C++14 it is possible to define binary literals, making the specification of bit masks even clearer:
if (x & 0b0010’0000) { ... }
As you can see from the example, not only can we specify the bitmask directly but we can also use ' as a digits separator to enhance readability even further. The generated assembly code is the same but the source is easier to understand.
Most modern languages like Python and C# support range-based loops; this doesn’t add more power to the language but it improves readability of the resulting code.
This functionality has been added to C++11 and it’s now possible to use range-based loops directly in your existing code.
Let’s take a look at an example:
int my_array[] = {1, 2, 3, 4, 5}; int sum_array(void) { int sum = 0; for (int i=0; i<5; i++) { sum += my_array[i]; } return sum; }
This can be re-written to
Int my_array[] = {1, 2, 3, 4, 5}; int sum_array(void) { int sum = 0; for (auto value : my_array) { sum += value; return sum; } }
The code reads better now and we also removed the size of the array from the for loop which is potential source of bugs (we need to update it if we add a new element for example).
The range-based for loop works with any type with begin() and end() function defined so that we can apply the same technique with std::vector:
int sum_array(std::vector<int> array) { int sum = 0; for (auto &value : array) { sum += value; } return sum; }
In this case the improvements in terms of readability are even better and, as a result, the code is easier to understand and maintain.
Since the beginning of the C standard, we have used NULL to check the validity of a pointer. This led to confusion in C++ because NULL is equivalent to 0.
Let’s assume we have two functions with the same name and different arguments:
Log_value(int value); // first function Log_value(char *value); // second function
In C++, the following code has an unexpected effect from the developer's point of view.
Log_value(NULL); // will call the first function
Infact, by using NULL we expect the second function to be called but, because NULL is equal to 0, the first function will be called instead.
In C++11 the keyword nullptr has been introduced and should be used instead of NULL, so that we can easily avoid this ambiguity:
Log_value(nullptr); // will call the second function
In this case, the second function is correctly called with an explicit null pointer value.
We have seen a few functionalities of C++11 and C++14 which can be used without worrying about performance and that can enhance the readability of your code. This article covers just a few of them, you can find more information on Wikipedia (C++11 - Wikipedia, the free encyclopedia and C++14 - Wikipedia, the free encyclopedia) and on Standard C++11 and Standard C++14.
I hope you found these information useful and you can soon start to use some of the functionalities in your code base. As mentioned at the beginning, ARM Compiler 6 supports C++11 and C++14. If you still don’t have DS-5, download a free 30-day evaluation of Ultimate Edition to get started.
Feel free to post any questions or comments below.
Ciao,
Stefano