Date: Wed, 3 Aug 2022 16:38:45 +0100
In my day job I program microcontrollers in C++, specifically the
Arduino ARM Cortex-M3 sam3x8e and the Texas Instruments F28069. Both
of these microcontrollers have 100 kB of volatile memory (i.e. memory
for storing intrinsic variables and objects at runtime).
I pretty much never use the heap, unless I have a very good reason to.
I prefer to have global objects of static duration, so that when I
compile my firmware, I can see the summary of how much memory I'm
using, and so there's no surprises like "throw bad_alloc()" at
runtime.
It would be very useful if the standard library had a "static
allocator" so that containers such as vector, set, map, could be used
easily on microcontrollers. This "static allocator" could also be used
on desktop PC's in situations where speed of execution is paramount.
For convenience, there could also be a namespace within 'std' called
'static_containers', something likes as follows:
namespace std {
namespace static_containers {
template<class T, std::size_t capacity>
using vector = ::std::vector< T, std::static_allocator<T,capacity> >;
template< class Key, class Compare = std::less<Key>,
std::size_t capacity >
using set = ::std::set< Key, Compare,
std::static_allocator<Key,capacity> >;
template< class Key, class T, class Compare = std::less<Key>,
std::size_t capacity >
using map = ::std::map< Key, T, Compare,
std::static_allocator< std::pair<Key const, T>, capacity > >;
}
}
Here's the code I have so far for a static allocator. I have to use
the preprocessor macro "__COUNTER__" along with the address of a
statically-linked variable to make sure each "static_alloc" is unique
(otherwise they would all be using the same piece of memory).
#include <cstddef> // size_t
#include <new> // bad_alloc, launder
// The address of the variable defined on the next line serves the same
// purpose as the preprocessor macro '__FILE__'
static int identifier_for_this_translation_unit;
template<typename T, std::size_t t_capacity, std::size_t t_counter,
int *t_file = &identifier_for_this_translation_unit>
class StaticAllocator {
public:
typedef T value_type;
protected:
alignas(value_type) static char buf[t_capacity*sizeof(value_type)];
public:
template<typename U>
struct rebind {
typedef StaticAllocator<U, t_capacity, t_counter, t_file> other;
};
value_type *allocate(std::size_t const n)
{
if ( n > t_capacity ) throw std::bad_alloc();
return std::launder(
static_cast<value_type*>(
static_cast<void*>(buf)));
}
void deallocate(value_type *, std::size_t)
{
/* Do Nothing */
}
};
template<typename T, std::size_t t_capacity, std::size_t t_counter,
int *t_file>
alignas(T) char StaticAllocator<T, t_capacity, t_counter, t_file>
::buf[t_capacity * sizeof(T)];
// =======================================
// =======================================
// =======================================
// Tester code begins below here
// =======================================
// =======================================
// =======================================
using std::size_t;
#include <vector>
using std::vector;
#include <utility>
using std::pair;
#include <iostream>
using std::cout;
using std::endl;
#define static_alloc(type,count) \
StaticAllocator< type, count, __COUNTER__ >
auto main(void) -> int
{
vector< char, static_alloc(char,4) > v1;
v1.push_back('a');
v1.push_back('b');
v1.push_back('c');
v1.push_back('d');
for ( auto const &elem : v1 ) cout << elem << endl;
vector< char, static_alloc(char,4) > v2;
v2.push_back('x');
v2.push_back('y');
v2.push_back('z');
for ( auto const &elem : v2 ) cout << elem << endl;
// Now try the first vector again
for ( auto const &elem : v1 ) cout << elem << endl;
// =================================================================
// ================ Now try pairs of doubles =======================
// =================================================================
typedef pair<double,int> Pair;
vector< Pair, static_alloc(Pair,12u) > v3;
v3.emplace_back(1.0, 12);
v3.emplace_back(2.0, 22);
v3.emplace_back(3.0, 32);
v3.emplace_back(4.0, 42);
for ( auto const &elem : v3 ) cout << elem.first << endl;
vector< Pair, static_alloc(Pair,4u) > v4;
v4.emplace_back(5.0, 12);
v4.emplace_back(6.0, 22);
v4.emplace_back(7.0, 32);
for ( auto const &elem : v4 ) cout << elem.first << endl;
// Now try the first vector again
for ( auto const &elem : v3 ) cout << elem.first << endl;
}
Arduino ARM Cortex-M3 sam3x8e and the Texas Instruments F28069. Both
of these microcontrollers have 100 kB of volatile memory (i.e. memory
for storing intrinsic variables and objects at runtime).
I pretty much never use the heap, unless I have a very good reason to.
I prefer to have global objects of static duration, so that when I
compile my firmware, I can see the summary of how much memory I'm
using, and so there's no surprises like "throw bad_alloc()" at
runtime.
It would be very useful if the standard library had a "static
allocator" so that containers such as vector, set, map, could be used
easily on microcontrollers. This "static allocator" could also be used
on desktop PC's in situations where speed of execution is paramount.
For convenience, there could also be a namespace within 'std' called
'static_containers', something likes as follows:
namespace std {
namespace static_containers {
template<class T, std::size_t capacity>
using vector = ::std::vector< T, std::static_allocator<T,capacity> >;
template< class Key, class Compare = std::less<Key>,
std::size_t capacity >
using set = ::std::set< Key, Compare,
std::static_allocator<Key,capacity> >;
template< class Key, class T, class Compare = std::less<Key>,
std::size_t capacity >
using map = ::std::map< Key, T, Compare,
std::static_allocator< std::pair<Key const, T>, capacity > >;
}
}
Here's the code I have so far for a static allocator. I have to use
the preprocessor macro "__COUNTER__" along with the address of a
statically-linked variable to make sure each "static_alloc" is unique
(otherwise they would all be using the same piece of memory).
#include <cstddef> // size_t
#include <new> // bad_alloc, launder
// The address of the variable defined on the next line serves the same
// purpose as the preprocessor macro '__FILE__'
static int identifier_for_this_translation_unit;
template<typename T, std::size_t t_capacity, std::size_t t_counter,
int *t_file = &identifier_for_this_translation_unit>
class StaticAllocator {
public:
typedef T value_type;
protected:
alignas(value_type) static char buf[t_capacity*sizeof(value_type)];
public:
template<typename U>
struct rebind {
typedef StaticAllocator<U, t_capacity, t_counter, t_file> other;
};
value_type *allocate(std::size_t const n)
{
if ( n > t_capacity ) throw std::bad_alloc();
return std::launder(
static_cast<value_type*>(
static_cast<void*>(buf)));
}
void deallocate(value_type *, std::size_t)
{
/* Do Nothing */
}
};
template<typename T, std::size_t t_capacity, std::size_t t_counter,
int *t_file>
alignas(T) char StaticAllocator<T, t_capacity, t_counter, t_file>
::buf[t_capacity * sizeof(T)];
// =======================================
// =======================================
// =======================================
// Tester code begins below here
// =======================================
// =======================================
// =======================================
using std::size_t;
#include <vector>
using std::vector;
#include <utility>
using std::pair;
#include <iostream>
using std::cout;
using std::endl;
#define static_alloc(type,count) \
StaticAllocator< type, count, __COUNTER__ >
auto main(void) -> int
{
vector< char, static_alloc(char,4) > v1;
v1.push_back('a');
v1.push_back('b');
v1.push_back('c');
v1.push_back('d');
for ( auto const &elem : v1 ) cout << elem << endl;
vector< char, static_alloc(char,4) > v2;
v2.push_back('x');
v2.push_back('y');
v2.push_back('z');
for ( auto const &elem : v2 ) cout << elem << endl;
// Now try the first vector again
for ( auto const &elem : v1 ) cout << elem << endl;
// =================================================================
// ================ Now try pairs of doubles =======================
// =================================================================
typedef pair<double,int> Pair;
vector< Pair, static_alloc(Pair,12u) > v3;
v3.emplace_back(1.0, 12);
v3.emplace_back(2.0, 22);
v3.emplace_back(3.0, 32);
v3.emplace_back(4.0, 42);
for ( auto const &elem : v3 ) cout << elem.first << endl;
vector< Pair, static_alloc(Pair,4u) > v4;
v4.emplace_back(5.0, 12);
v4.emplace_back(6.0, 22);
v4.emplace_back(7.0, 32);
for ( auto const &elem : v4 ) cout << elem.first << endl;
// Now try the first vector again
for ( auto const &elem : v3 ) cout << elem.first << endl;
}
Received on 2022-08-03 15:38:57