On Wed, Aug 3, 2022 at 11:39 AM Frederick Virchanza Gotham via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
In my day job I program microcontrollers in C++ [...]
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.

That doesn't make sense. You say you basically never use dynamic allocation... and then you say you'd like to use containers like vector, set, and map, which are dynamically sized and use dynamic allocation by definition.

A "container" like std::array<T,N> or boost::static_vector<T, Cap> is statically sized: it has a fixed capacity, and if you try to exceed that capacity (e.g. with push_back in a loop) then you get an exception (or UB). A container like std::vector<T> is dynamically sized: if you do push_back in a loop, it will just keep asking the allocator for more memory, until at some point the allocator fails (e.g. by throwing an exception).

The way you run out of memory and get bad_alloc exceptions is by putting millions of items into your containers. You can't fix that problem by switching allocators; you still have only a finite amount of memory on your machine. The way to fix that problem is to stop putting millions of items into your containers; i.e., restrict yourself to algorithms that require only fixed-size (or fixed-capacity) containers, so that you don't need dynamically sized containers.

If you're just looking for a way to audit your program's usage of dynamic memory over time, you should first try overriding `operator new`, or look into platform-specific ways to check total memory usage, such as maybe the zoo of mallctl statistics provided by http://jemalloc.net/jemalloc.3.html .
You will also be interested in Attila Feher's proposal for a std::pmr::test_memory_resource:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1160r1.pdf
(That proposal is dead AFAIK, and IMHO rightly so; but it exists as a third-party library that you can plug in to your own codebase if your codebase uses std::pmr. Which it doesn't, because you don't use dynamic allocation, right? ;))


[...]
template<typename T, std::size_t t_capacity, std::size_t t_counter,
        int *t_file = &identifier_for_this_translation_unit>
class StaticAllocator {
[...] 
protected:
    alignas(value_type) static char buf[t_capacity*sizeof(value_type)];

This won't work. An allocator cannot hold container elements inside itself, because then what happens when you make a copy of the allocator, or std::move the allocator from one place to another? All your pointers break!
Watch my talk "An Allocator is a Handle to a Heap" to see how C++'s allocator model works.
https://www.youtube.com/watch?v=0MdSJsCTRkY&ab_channel=CppNow

    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)));

Two problems here: One, this memory resource doesn't actually bump its pointer when it allocates, so I can't call `allocate` more than once (unless I `deallocate` in between). This means you can't use this memory resource with any container that makes dynamic allocations — std::deque or std::set, for example, but also std::vector if you intend to ever `push_back` anything onto it.
Two, this memory resource can still throw bad_alloc, which is exactly the problem you were complaining about!  This was my point above: You can fiddle with the allocator internals all you want, but you'll never escape the essential problem of dynamic allocation, which is that you don't know how much memory you're going to need until runtime. (If you did know, you wouldn't need dynamic allocation.)

HTH,
Arthur