I cannot answer this question for Arduino, but for atmega32u4 and C++ in general.
First of all, it depends what you mean with growing. On other platform this could be conveniently done using dynamic memory allocation. But this is not a good idea on atmegas as its RAM is too limited and many consecutive allocations would require extra RAM for the bookkeeping of the malloc
system. That’s even worse when memory is freed as this can lead to fragmentation. Also malloc
uses a lot of PROGMEM. That’s why dynamic allocation is usually not used on embedded systems.
Which leaves us with static memory allocation. Your data structure, no matter how it is exactly designed would need to reserve the maximum amount of RAM needed at compile time, e.g. by using statically sized arrays.
The choice of container depends on the type of application (iteration vs. insertion/removal). In your case, a set-type data structure would be optimal (due to the volatile size), but you also probably want to iterate over the list.
If the order of the data stored does not matter, a simple array can hold the entries and there could be an additional index variable that would store the current largest array index used. Any unused items could be tagged with some sort of ‘unused’ value, e.g. 255
. When used alone, the iteration complexity always equals the size of the list. Another approach can be to have an auxiliary vector that holds the indices that are currently unused. Thus there would not be any holes in the list and both, iteration and insertion would be fast. I have implemented such a thing probably a hundred times, but currently I do not find an appropriate source code that is simple enough to serve as an example.
Another approach, a static sized doubly linked list can be implemented using two arrays and one or two additional uint8_t
as follows.
struct ListEntry {
FooType value_;
uint8_t nextEntry_;
uint8_t prevEntry_;
};
...
ListEntry list[maxKeys];
uint8_t freeEntries[maxKeys];
for(uint8_t id = 0; id < maxKeys; ++id) { freeEntries[id] = id; }
uint8_t nextEntry = 0;
uint8_t listStart = 0;
uint8_t listEnd = 0;
Adding list entries: The next entry will stored at list[freeEntries[nextEntry]]
, then nextEntry
will be incremented. Then .prevEntry
and .nextEntry
will be set accordingly.
Removing entries: nextEntry
will be decremented and the position of the entry removed will be stored in freeEntries[nextEntry]
. listStart
and listEnd
are adjusted if removing at front of back.
A singly linked list is even simpler to implement.