Please, before submitting a support request read carefully this README and check if an answer already exists among previously answered questions: do not abuse of the Github issue tracker.
The library itself has an implicit memory consumption of about 0.5Kb: 580 bytes (max) of code and 8 bytes of memory, according to my calculations. That does not consider the space used to store the items themselves, obviously.
When declaring your buffer you should specify the data type it must handle and the buffer capacity: those two parameters will influence the memory consumed by the buffer.
#include <CircularBuffer.hpp>
CircularBuffer<byte,100> bytes; // uses 538 bytes
CircularBuffer<int,100> ints; // uses 638 bytes
CircularBuffer<long,100> longs; // uses 838 bytes
CircularBuffer<float,100> floats; // uses 988 bytes
CircularBuffer<double,100> doubles; // uses 988 bytes
CircularBuffer<char,100> chars; // uses 538 bytes
CircularBuffer<void*,100> pointers; // uses 638 bytes
Please note: the memory usage reported above includes the program memory used by the library code, the heap memory is much less and is comparable to an array of the same size and type of the buffer.
Let's start making things clear: the library doesn't support inserting data in the middle of the buffer.
You can add data to the buffer either before the first element via an unshift()
operation or after the last element via a push()
operation.
You can keep adding data beyond the buffer maximum capacity, but you'll lose the least significant information:
unshift()
adds to the head, adding beyond capacity causes the element at tail to be overwritten and lostpush()
adds to the tail, adding beyond capacity causes the element at head to be overwritten and lostBoth unshift()
and push()
return true
if the addition didn't cause any information loss, false
if an overwrite occurred:
CircularBuffer<int,5> buffer; // buffer capacity is 5
// all of the following return true
buffer.unshift(1); // [1]
buffer.unshift(2); // [2,1]
buffer.unshift(3); // [3,2,1]
buffer.push(0); // [3,2,1,0]
buffer.push(5); // [3,2,1,0,5]
// buffer is now at full capacity, from now on any addition returns false
buffer.unshift(2); // [2,3,2,1,0] returns false
buffer.unshift(10); // [10,2,3,2,1] returns false
buffer.push(-5); // [2,3,2,1,-5] returns false
Similarly to data addition, data retrieval can be performed at tail via a pop()
operation or from head via a shift()
operation: both cause the element being read to be removed from the buffer.
Reading data beyond the actual buffer size has an undefined behaviour and is user's responsibility to prevent such boundary violations using the additional operations listed in the next section. The library will behave differently depending on the data type and allocation method, but you can safely assume your program will crash if you don't watch your steps.
Non-destructive read operations are also available:
first()
returns the element at head
last()
returns the element at tail
[]
operatorCircularBuffer<char, 50> buffer; // ['a','b','c','d','e','f','g']
buffer.first(); // ['a','b','c','d','e','f','g'] returns 'a'
buffer.last(); // ['a','b','c','d','e','f','g'] returns 'g'
buffer.pop(); // ['a','b','c','d','e','f'] returns 'g'
buffer.pop(); // ['a','b','c','d','e'] returns 'f'
buffer.shift(); // ['b','c','d','e'] returns 'a'
buffer.shift(); // ['c','d','e'] returns 'b'
buffer[0]; // ['c','d','e'] returns 'c'
buffer[1]; // ['c','d','e'] returns 'd'
buffer[2]; // ['c','d','e'] returns 'e'
buffer[10]; // ['c','d','e'] returned value is unpredictable
buffer[15]; // ['c','d','e'] returned value is unpredictable
isEmpty()
returns true
only if no data is stored in the bufferisFull()
returns true
if no data can be further added to the buffer without causing overwrites/data losssize()
returns the number of elements currently stored in the buffer; it should be used in conjunction with the []
operator to avoid boundary violations: the first element index is always 0
(if buffer is not empty), the last element index is always size() - 1
available()
returns the number of elements that can be added before saturating the buffercapacity()
returns the number of elements the buffer can store, for completeness only as it's user-defined and never changes REMOVED from 1.3.0
replaced by the read-only member variable capacity
clear()
resets the whole buffer to its initial state (pay attention though, if you had dynamically allocated objects in your buffer, memory used by such object is not released: iterate over the buffer contents and release objects accordingly to their allocation method)copyToArray(array)
copies the contents of the buffer to a standard array array
. The array must be large enough to hold all the elements currently in the buffer.copyToArray(conversionFn, array)
copies the contents of the buffer to a standard array array
executing a function on each element, usually a type conversion. The array must be large enough to hold all the elements currently in the buffer.Starting from version 1.3.0
the library is capable to automatically detecting which data type should be used for the index based on the buffer capacity:
65535
then your index is going to be an unsigned long
unsigned int
for buffers with a declared capacity greater than 255
byte
is going to sufficeIn addition, you can mix in the same code buffers with small index and buffers with normal index: previously this was not possible.
CircularBuffer<char,100> optimizedBuffer; // reduced memory footprint, index type is uint8_t (a.k.a. byte)
CircularBuffer<long,500> normalBuffer; // standard memory footprint, index type is unit16_t (a.k.a. unsigned int)
CircularBuffer<int,66000> hugeBuffer; // extended memory footprint, index type is unit32_t (a.k.a. unsigned long)
To obtain the maximum advantage of the optimization above, anytime you need to refer to the buffer index you should use the most appropriate type: this can be easily achieved using the decltype
specifier, like in the following example:
// the iterator variable i is of the correct type, even if
// we don't know what's the buffer declared capacity
for (decltype(buffer)::index_t i = 0; i < buffer.size(); i++) {
avg += buffer[i] / buffer.size();
}
If you prefer, you can alias the index type and refer to such alias:
using index_t = decltype(buffer)::index_t;
for (index_t i = 0; i < buffer.size(); i++) {
avg += buffer[i] / buffer.size();
}
The following applies to versions prior to 1.3.0
only.
By default the library uses unsigned int
indexes, allowing for a maximum of 65535
items, but you'll rarely need such a huge store.
You can switch the library indexes to byte
type defining the CIRCULAR_BUFFER_XS
macro BEFORE the #include
directive: this reduces the memory used by the library itself by only 36
bytes, but allows you to potentially squeeze out much more whenever you perform an indexed access, if you do any, by using the smaller data type.
#define CIRCULAR_BUFFER_XS
#include <CircularBuffer.h>
CircularBuffer<short,100> buffer;
void setup() { }
void loop() {
// here i should be declared of type byte rather than unsigned int
// in order to maximize the effects of the optimization
for (byte i = 0; i < buffer.size() - 1; i++) {
Serial.print(buffer[i]);
}
}
Please note: this macro switch forces the buffer to use an 8 bits data type as the internal index, as such all your buffers will be limited to a maximum capacity of 255
.
The library does help to work with interrupts defining the CIRCULAR_BUFFER_INT_SAFE
macro switch, which introduces the volatile
modifier to the count
variable, making the whole library more interrupt-friendly at the price of disabling some compiler optimizations. The #define
statement needs to be put somewhere before the #include
statement:
#define CIRCULAR_BUFFER_INT_SAFE
#include <CircularBuffer.h>
CircularBuffer<unsigned long, 10> timings;
void count() {
timings.push(millis());
}
void setup() {
attachInterrupt(digitalPinToInterrupt(2), count, RISING);
}
void loop() {
Serial.print("buffer size is "); Serial.println(timings.size());
delay(250);
}
Please note this does NOT make the library interrupt safe, but it does help its usage in interrupt driven firmwares.
Multiple examples are available in the examples
folder of the library:
copyToArray()
functions.If you use this library to store dynamically allocated objects, refrain from using the clear()
method as that will not perform memory deallocation: you need to iterate over your buffer content and release memory accordingly to the allocation method used, either via delete
(if you had used new
) or free
(in case of malloc
):
while (!buffer.isEmpty()) {
// pick the correct one
delete buffer.pop();
free(buffer.pop());
}
The very same applies for the pop()
and shift()
operations as any dynamically allocated object is only detached from the buffer, but the memory it uses is not automagically released (see the Object.ino example)
Record* record = new Record(millis(), sample); // a dynamically allocated object
buffer.push(record);
// somewhere else
if (!buffer.isEmpty()) {
Record* current = buffer.pop();
Serial.println(current.value());
delete current; // if you don't do this the object memory is lost!!!
}
copyToArray(array)
and copyToArray(array, convertFn)
.h
header extension to .hpp
shift()
and pop()
operations misuse to mess up the buffer[]
operatorabort()
is AVR-specificMost of the major improvements below have been contributed by Erlkoenig90: thank you Niklas!
capacity()
in favor of the constant instance attribute capacity
EventLogging
and Interrupts
examplesCIRCULAT_BUFFER_XS
macro switch in favor of automatic index type identificationUINT32_MAX
)CIRCULAR_BUFFER_INT_SAFE
memset
when clearingclear()
functionpop()
functionpop()
and shift()
implementationscapacity()
functiondebug()
function, disabled by pre-processor by default