MemoryRange
March 30, 2026 ยท View on GitHub
Embedded applications regularly handle blocks of memory. Such memory may either be typed (for example, consisting of a number of characters) or un-typed. Traditionally, blocks of memory are handled by having a pointer to its first element and a separate size. Since passing around two separate but related parameters is cumbersome at best and error-prone at its worst, we have improved on dealing with blocks of memory by introducing the class template MemoryRange. A MemoryRange is an object which defines a block of memory with a start and an end, and which has a number of functions that makes dealing with those blocks as a whole easier. This approach reduces the potential for buffer overflows, since while it is easy to shrink a block or to split it; it is much more difficult to create a larger block, thereby overflowing the original block of memory.
The MemoryRanges that are most often used are MemoryRange<uint8_t>, which has an alias ByteRange, and its const counterpart ConstByteRange which aliases to MemoryRange<const uint8_t>. Everywhere where untyped memory is used ByteRange or ConstByteRange is used to define where the data resides in memory. For example: the SPI interface SendData method takes a ConstByteRange parameter specifying the data which should be sent.
A MemoryRange does not include the storage it points to. Modifying a MemoryRange does not modify the storage. If storage is needed, std::array is a good candidate. After creating a std::array and filling it with data, a MemoryRange can be constructed from it by using MakeRange, after which the MemoryRange can be used for passing around the data.
MemoryRange Accessors
These are the most used accessors of MemoryRange:
| Method | Description |
|---|---|
begin() | Returns a pointer to the first element of the data |
end() | Returns a pointer to one position beyond the last element of the data |
empty() | Returns true if and only if size() == 0 |
size() | Returns the number of elements pointed to: end() - begin() |
pop_front(num) | Remove num elements from the front of the range |
pop_back(num) | Remove num elements from the back of the range |
shrink_from_front_to(newSize) | If size() > newSize, pop elements from the front until size() == newSize |
shrink_from_back_to(newSize) | If size() > newSize, pop elements from the back until size() == newSize |
Helper Functions
In addition to the members of MemoryRange, a number of helper functions exist:
| Function | Description |
|---|---|
MakeRange(...) | Convert the parameter(s) to a MemoryRange. Works with std::array, infra::BoundedVector, etc. |
Head(range, size) | Returns range if its size is less than size, otherwise a new range consisting of the first size elements of range |
Tail(range, size) | Returns range if its size is less than size, otherwise a new range consisting of the last size elements of range |
DiscardHead(range, size) | Returns an empty range if size is greater than range.size(), otherwise a new range consisting of range except for the first size elements |
DiscardTail(range, size) | Returns an empty range if size is greater than range.size(), otherwise a new range consisting of range except for the last size elements |
Example
This example function shows how to use the MemoryRange functions to cut up a block of data into smaller pieces:
void SendInBlocks(infra::ConstByteRange sendData, std::size_t blockSize)
{
while (!sendData.empty())
{
Send(infra::Head(sendData, blockSize));
sendData = infra::DiscardHead(sendData, blockSize);
}
}
void Example()
{
std::array<uint8_t, 256> data = { ... };
SendInBlocks(data, 16);
}