Date: Wed, 3 Aug 2022 14:58:31 -0400
On Wed, Aug 3, 2022 at 11:39 AM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> 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
Std-Proposals <std-proposals_at_[hidden]> 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
Received on 2022-08-03 18:58:43