avr-libstdcpp
is a partial, non-fully-tested
implementation of the C++ standard library and its STL.
It is intended to be used with avr-gcc
.
Many features of modern C++11,14,17 and 20 are supported.
avr-gcc
does not have a complete C++ standard library nor does it include an STL implementation.
The avr-libstdcpp
port (even though not-fully-tested
and only partially full/complete) will, nonetheless,
be useful for those interested in making more comprehensive utilization
of C++, including its standard library, with a modern avr-gcc
compiler.
The avr-libstdcpp
library traces its own origins to existing
GNU/GCC C++ standard library implementation(s), themselves targeting
embedded systems. This work is essentially an even-more
embedded-friendly adaptation of the aforementioned work.
The avr-libstdcpp
port began in 2018 with an initial import
of a GNU/GCC-based C++ standard library from GCC 8.
A second import of a GNU/GCC-based C++ standard library from GCC 10
in 2020 modernized the port to include many modern C++20 features.
- Add the
avr-libstdcpp/include
path to the standard-I
include path(s) of the compiler on the command line. - Upon doing this, include standard library headers in the usual way (i.e.,
#include <algorithm>
,#include <array>
,#include <cstdint>
, etc.). - There are also a handful of source files located in the src directory. Some of these may potentially be needed.
- For instance, when doing floating-point mathematical calculations with the
<cmath>
library, the filemath.cc
located here needs to be added as a normal source file to your project.
For straightforward header-only use, for example,
simply add the -isystem
(or alternatively the -I
) include path
to your particular location of avr-libstdcpp/include
on the command line...
avr-g++ -O2 -x c++ -isystem /my_path/avr-libstdcpp/include -mmcu=atmega328p test.cpp -o test.elf
... and seamlessly use standard library headers in your code.
#include <array>
#include <numeric>
std::array<int, 3U> a { 1, 2, 3 };
int main()
{
// 6
auto sum = std::accumulate(a.cbegin(), a.cend(), 0);
static_cast<void>(sum);
}
Additional straightforward code samples exercising standard library usage can be found in the examples folder.
avr-libstdcpp
can be successfully used with MICROCHIP's ATMEL Studio.
The include path of the headers needs to be added to the project settings in the normal way.
Add also any of the necessary source files, as described in the section above.
This is an advanced use of avr-libstdcpp
in combination with MICROCHIP's ATMEL Studio
because the underlying GCC compiler used with ATMEL Studio also needs to be
upgraded to a much more modern one than the avr-gcc
5
delivered in the standard installation of this studio.
An informative thread
provides a few more details on how to use avr-libstdcpp
with MICROCHIP's ATMEL Studio.
In general the C++ standard library is intended to be written and implemented in a resource-sensitive fashion. This includes efforts to save on both memory as well as run-time. In fact, C++ standard library functions and algorithms have, in general, been specifically written and tuned by the library authors with efficiency aspects in mind. In particular, library components compile reliably and quickly and also lend themselves well to compiler optimization.
Some library components, however, are particularly well-suited for bare-metal microcontroller programming. These can be exceptionally helpful when used properly and sensibly in tiny bare-metal microcontroller environments.
A subjective list of these the libraries/headers and their main uses includes, but is not limited to,:
<array>
for containers having known, fixed size.<algorithm>
for standard algorithms such as sorting, minimax, sequential operations, etc.<cmath>
for projects requiring floating-point mathematical functions such asstd::sin()
,std::exp()
,std::frexp()
and many more. For some mathematical uses, it might be necessary to includemath.cc
in your project. This source file is located here.<cstdint>
which defines integral types having specified widths residing withinnamespace std
likestd::uint8_t
.<limits>
offering compile-time query of numeric limits of built-in types.<numeric>
featuring a collection of useful numeric algorithms such asstd::accumulate()
, etc.<type_traits>
for compile-time decisions based on types.
With these libraries alone, the entire project can benefit from a great deal of the standard library's power without compromising in any way on performance or sleek memory footprint. This is because these libaries typically require no additional storage.
The following non-trivial, real-world example, for instance,
wraps instances of an overly-simplified LED class abstraction
as object-references in an std::array
.
Once stored, the application exercises the LED's toggle
function in an algorithmic loop with toggle()
-method call
expressed via lambda function.
#include <algorithm>
#include <array>
#include <type_traits>
class led
{
public:
led() = default;
auto toggle() -> void { }
};
led led0;
led led1;
led led2;
using led_ref_type = std::reference_wrapper<led>;
std::array<led_ref_type, 3U> led_refs =
{
led0,
led1,
led2
};
int main()
{
for(;;)
{
std::for_each(led_refs.begin(),
led_refs.end(),
[](led& lr) { lr.toggle(); });
}
}
This nifty little example is terse, expressive and powerful.
It makes use of parts of <algorithm>
, <array>
and <type_traits>
to greatly simplify the programming within a non-trivial
microcontroller situation.
This example is key because it combines the domains of object-oriented programming with the templated algorithms and wrappers of the STL to assist in our microcontroller world.
Some C++ library and STL artifacts, however, require more careful design considerations regarding memory allocation and management.
Consider, for instance, std::vector
from the <vector>
library.
Vector creates a flexibly-sized array-like collection
of items of any kind, depending on the template parameter.
For instance:
#include <vector>
// A vector of 3 integers.
std::vector<int> v { 1, 2, 3 };
See also the main.cpp file in the ./examples/vector directory.
This vector requires storage for three integers which,
on the avr-gcc
platform is 6 bytes. The storage is managed
through vector's second, less well-known template parameter.
In other words,
namespace std {
// Forward declaration of the vector template class.
template<typename T,
typename AllocatorType = std::allocator<T>>
class vector;
}
Using containers requires memory allocation with a so-called allocator.
If none is specified, as in our code snippet, the default allocator
from namespace std
for the templated type T
of the vector is
automatically selected.
Good embeddable self-written custom allocators are essential for using such containers so that memory could be managed with a self-written memory pool, an off-chip memory device, etc. A common selection is a pool of static memory creating a so-called ring allocator. This is an intermetiate/advanced topic which will refine STL use on the metal and also allow for flexible template use in these resource-sensitive realms.
Some parts of the C++ standard library are not well suited for tiny bare-metal systems. These include some memory-intensive and/or hardware-intensive library artifacts.
avr-libstdcpp
has the following known adaptions and limitations.
-
I/O streaming and RTTI: I/O streaming and run-time type information (RTTI) are known to be resource-intensive and could be disruptive on tiny embedded platforms. An effort has been made to essentially remove both these library dependencies and their associated codes.
-
exceptions: Exceptions are also difficult to efficiently implement on tiny embedded platforms and this library port avoids using exceptions. The headers
<stdexcept>
and<exception>
, their dependencies, and their directly relevant code sequences have been removed. Simple mechanisms such as those found in<cassert>
and<cerrno>
, however, remain mostly available. -
<atomic>
: Even though the intended compilers are built with no threading, the<atomic>
library and its use as a dependency has been removed. This means that atomic functions and atomic store/load functions are not available. So if you are sharing data in an operating system or mixed program/interrupt mode, self-written atomic primitives are needed. -
<random>
: There is no source of entropy whatsoever on these platforms in their standard configuration. Sostd::random_device
has been removed. -
Hashing: Hashing has been optimized for tiny architectures and uses a rudimentary 16-bit CRC algorithm.
-
<chrono>
: Only certain selected the clock functions of<chrono>
library are implemented. These includestd::chrono::high_resolution_clock
andstd::chrono::steady_clock
. When using these clocks, it is required to implement the clock's static methodnow()
in a project-specific fashion. This is because the library's authors can not in a generic way implement any microcontroller-specific clock(s) since this requires detailed knowledge of the underlying microcontroller peripherie. -
int
,size_t
,ptrdiff_t
and the like: Data types such asint
, andsize_t
andptrdiff_t
(which are aliased tounsigned
/signed
versions ofint
) are generally limited to 16-bits in width on tinyavr-gcc
platforms. Although this is a compiler attribute, it has strong influence on the library (particularly the STL) implementation because these data types are used copiously therein. This compiler attribute limits ranges, indexes, etc. to 16-bits. With the compiler switch-mint8
, the built-in typeint
is only 8 bits wide and extreme range limitations are expected to make STL use tricky. -
<cmath>
: Inavr-gcc
10 and higher, the built-in data typesdouble
andlong double
can be either 32 or 64 bits in width. The widths depend on the compiler command line options-mdouble=32
(alternatively-mdouble=64
) and/or-mlong-double=32
(alternatively-mlong-double=64
). Standard floating-point<cmath>
functions such asstd::sin()
,std::cos()
,std::exp()
and the like will, therefore, have input and output widths according to these command line options. -
<cmath>
: In compiler versions ofavr-gcc
11 and higher, slight discrepancies in the signatures of functions likeisnan()
,isinf()
, etc. seem to be in the process of being corrected. Future patches of math function signatures in<cmath>
may be needed as the<math.h>
header continues to evolve.
The following is a rather advanced, highly useful topic.
When using C++20, constexpr
construction, assignment and evaluation
of various algorithms can and often will be generally
compile-time constant (i.e, via consistent use of C++20 constexpr
-ness).
As a result of this, STL algorithms that use compile-time constant inputs are, in fact, evaluated at compile time in C++20. This lets us perform a strong, purposeful shift of algorithmic complexity to-the-left. In other words, we shift algorithmic complexity into the compile-time stage of code development and away from the precious RAM-ROM-space/cycles of the compiled running code.
In the following code, for instance, we revisit the std::array
/<numeric>
example from above. The variation below exhibits complete compile-time
evaluation of the algorithmic result.
To take the deep dive in this topic, follow all the useful compile-time
preprocessor symbols such as
__cpp_lib_constexpr_algorithms
, __cpp_lib_constexpr_numeric
, and many more
in feature testing.
#include <array>
#include <numeric>
#if (defined(__cpp_lib_constexpr_numeric) && (__cpp_lib_constexpr_numeric>=201911L))
#define MODM_CONSTEXPR constexpr
#define MODM_CONSTEXPR_NUMERIC_IS_CONSTEXPR 1
#else
#define MODM_CONSTEXPR
#define MODM_CONSTEXPR_NUMERIC_IS_CONSTEXPR 0
#endif
MODM_CONSTEXPR std::array<int, 3U> a { 1, 2, 3 };
int main()
{
// 6
auto MODM_CONSTEXPR sum = std::accumulate(a.cbegin(), a.cend(), 0);
#if (MODM_CONSTEXPR_NUMERIC_IS_CONSTEXPR == 1)
static_assert(sum == 6, "Error: Unexpected std::accumulate result!");
#endif
return (sum == 6 ? 0 : -1);
}
See also the numeric.cpp file in the ./examples/numeric directory.
avr-libstdcpp
is intended for a modern avr-gcc
such as the 11.2 port available in the modm-io project
repository. Tests show usability also for avr-gcc
10.
Using the port way back to avr-gcc
5, for instance, does not work
at the moment in today's form of the checked-in library,
as the older compiler's lexical parser is not capable of
properly handling some of the library's template code.