@Adam Rosenfield's solution, although correct, could be implemented with a more lightweight circular_buffer structure that does not involve count and capacity.
The structure could only hold the following 4 pointers:
buffer: Points to the start of the buffer in memory.
buffer_end: Points to the end of the buffer in memory.
head: Points to the end of stored data.
tail: Points to the start of stored data.
We could keep the sz attribute to allow the parametrisation of the unit of storage.
Both the count and the capacity values should be derive-able using the above pointers.
Capacity
capacity is straight forward, as it can be derived by dividing the distance between the buffer_end pointer and the buffer pointer by the unit of storage sz (snippet below is pseudocode):
capacity = (buffer_end - buffer) / sz
Count
For count though, things get a bit more complicated. For example, there is no way to determine whether the buffer is empty or full, in the scenario of head and tail pointing to the same location.
To tackle that, the buffer should allocate memory for an additional element. For example, if the desired capacity of our circular buffer is 10 * sz, then we need to allocate 11 * sz.
Capacity formula will then become (snippet below is pseudocode):
capacity_bytes = buffer_end - buffer - sz
capacity = capacity_bytes / sz
This extra element semantic allows us to construct conditions that evaluate whether the buffer is empty or full.
Empty state conditions
In order for the buffer to be empty, the head pointer points to the same location as the tail pointer:
head == tail
If the above evaluates to true, the buffer is empty.
Full state conditions
In order for the buffer to be full, the head pointer should be 1 element behind the tail pointer. Thus, the space needed to cover in order to jump from the head location to the tail location should be equal to 1 * sz.
if tail is larger than head:
tail - head == sz
If the above evaluates to true, the buffer is full.
if head is larger than tail:
buffer_end - head returns the space to jump from the head to the end of the buffer.
tail - buffer returns the space needed to jump from the start of the buffer to the `tail.
- Adding the above 2 should equal to the space needed to jump from the
head to the tail
- The space derived in step 3, should not be more than
1 * sz
(buffer_end - head) + (tail - buffer) == sz
=> buffer_end - buffer - head + tail == sz
=> buffer_end - buffer - sz == head - tail
=> head - tail == buffer_end - buffer - sz
=> head - tail == capacity_bytes
If the above evaluates to true, the buffer is full.
In practice
Modifying @Adam Rosenfield's to use the above circular_buffer structure:
#include <string.h>
#define CB_SUCCESS 0 /* CB operation was successful */
#define CB_MEMORY_ERROR 1 /* Failed to allocate memory */
#define CB_OVERFLOW_ERROR 2 /* CB is full. Cannot push more items. */
#define CB_EMPTY_ERROR 3 /* CB is empty. Cannot pop more items. */
typedef struct circular_buffer {
void *buffer;
void *buffer_end;
size_t sz;
void *head;
void *tail;
} circular_buffer;
int cb_init(circular_buffer *cb, size_t capacity, size_t sz) {
const int incremented_capacity = capacity + 1; // Add extra element to evaluate count
cb->buffer = malloc(incremented_capacity * sz);
if (cb->buffer == NULL)
return CB_MEMORY_ERROR;
cb->buffer_end = (char *)cb->buffer + incremented_capacity * sz;
cb->sz = sz;
cb->head = cb->buffer;
cb->tail = cb->buffer;
return CB_SUCCESS;
}
int cb_free(circular_buffer *cb) {
free(cb->buffer);
return CB_SUCCESS;
}
const int _cb_length(circular_buffer *cb) {
return (char *)cb->buffer_end - (char *)cb->buffer;
}
int cb_push_back(circular_buffer *cb, const void *item) {
const int buffer_length = _cb_length(cb);
const int capacity_length = buffer_length - cb->sz;
if ((char *)cb->tail - (char *)cb->head == cb->sz ||
(char *)cb->head - (char *)cb->tail == capacity_length)
return CB_OVERFLOW_ERROR;
memcpy(cb->head, item, cb->sz);
cb->head = (char*)cb->head + cb->sz;
if(cb->head == cb->buffer_end)
cb->head = cb->buffer;
return CB_SUCCESS;
}
int cb_pop_front(circular_buffer *cb, void *item) {
if (cb->head == cb->tail)
return CB_EMPTY_ERROR;
memcpy(item, cb->tail, cb->sz);
cb->tail = (char*)cb->tail + cb->sz;
if(cb->tail == cb->buffer_end)
cb->tail = cb->buffer;
return CB_SUCCESS;
}