C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Static Allocator (All Containers Compatible with Microcontrollers)

From: Julien Villemure-Fréchette <julien.villemure_at_[hidden]>
Date: Thu, 4 Aug 2022 00:03:56 +0000
Its hard to get around bad_alloc. At least you would need to know at compile time the upper bound of allocations for each function that would allocate. This implies that you only preallocate constant size arrays of char (and that you have powerful static analysis tools).

The allocator model requires that stateful allocators have canonical reference semantics (ex: holds a single raw pointer, copy == move). The closest thing to what you propose that I think of is to construct a
`std::pmr::monotonic_buffer_resource` from a statically size array of `std::byte` and wrap this with a polymorphic allocator (for use as the last argument of a (pmr) container's constructor). You'll most probably also want to call
```
set_default_resource(
  std::pmr::null_memory_resource());
// or some custom equivalent which
// reports some context before terminating
```
at the beginning of your entry point, as to ensure that you keep tight control on which allocator each container uses.

Julien V.

Get Outlook for Android<https://aka.ms/AAb9ysg>
________________________________
From: Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf of Frederick Virchanza Gotham via Std-Proposals <std-proposals_at_[hidden]>
Sent: Wednesday, August 3, 2022 11:38:45 AM
To: std-proposals <std-proposals_at_[hidden]>
Cc: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Subject: [std-proposals] Static Allocator (All Containers Compatible with Microcontrollers)

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;
}
--
Std-Proposals mailing list
Std-Proposals_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2022-08-04 00:04:00