This is a follow up to this post I uploaded a few days back on my alternative account about a circular buffer in C. I'm confident that I fixed most of the warnings and even added an iterator.
circularBuffer.h
#ifndef CIRCULAR_BUFFER_H
#define CIRCULAR_BUFFER_H
#include <stddef.h> // max_align_t in source
struct circularBuffer;
// circularBufferCreate: {{{
// Creates a new circularBuffer in memory and returns a pointer to it.
//
// @param capacity The capacity of the new circularBuffer.
// @param elementSize The element size of the contained elements.
// Expected to be greater than zero.
//
// @return Pointer to the created circularBuffer in memory.
// NULL on allocation fail or false arguments. }}}
struct circularBuffer *circularBufferCreate(size_t capacity, size_t elementSize);
// circularBufferDestroy: {{{
// Destroys the passed circularBuffer struct in memory.
//
// @param buf The circularBuffer, which is to be destroyed. }}}
void circularBufferDestroy(struct circularBuffer *buf);
// circularBufferCardinality: {{{
// Returns the current number of elements stored in the passed circularBuffer.
//
// @param buf Pointer to the circularBuffer to get cardinality from;
//
// @return The number of elements currently stored in the passed circularBuffer. }}}
size_t circularBufferCardinality(const struct circularBuffer *buf);
// circularBufferIsEmpty: {{{
// Checks whether a circularBuffer is empty.
//
// @param buf Pointer to the circularBuffer to check emptyness of (not NULL).
//
// @return Non-zero when empty, zero otherwise. }}}
int circularBufferIsEmpty(const struct circularBuffer *buf);
// circularBufferIsFull: {{{
// Checks whether a circularBuffer is full.
//
// @param buf Pointer to the circularBuffer to check fullness of (not NULL).
//
// @return Not zero when full, zero otherwise. }}}
int circularBufferIsFull(const struct circularBuffer *buf);
// circularBufferPushHead: {{{
// Pushes the data pointed to by ptr onto the circularBuffers head.
//
// @param buf CiruclarBuffer the data is pushed onto.
// @param ptr Data to be pushed. }}}
void circularBufferPushHead(struct circularBuffer *buf, void *ptr);
// circularBufferPopTail: {{{
// Pops current tail element of passed circularBuffer and returns pointer to it.
//
// @param buf circularBuffer, which tail should be popped (NON-NULL-REQUIRED).
//
// @return Pointer to popped data.
// This pointer is only valid until the data is overwritten internally.
// For later use copying is necessary. }}}
char *circularBufferPopTail(struct circularBuffer *buf);
// circularBufferPeekHead: {{{
// Returns pointer to the current head element of the buffer.
//
// @param buf CircularBuffer, on which head a peek is wanted (NON-NULL-REQUIRED).
//
// @return Pointer to the current head of the passed circular buffer.
// NULL in the case of an empty buffer.
// }}}
const char *circularBufferPeekHead(const struct circularBuffer *buf);
// circularBufferPeekTail: {{{
// Returns pointer to the current tail element of the buffer.
//
// @param buf CircularBuffer, on which tail a peek is wanted (NON-NULL-REQUIRED).
//
// @return Pointer to the current tail of the passed circular buffer.
// NULL in the case of an empty buffer.
// }}}
const char *circularBufferPeekTail(const struct circularBuffer *buf);
struct circularBufferIterator;
// circularBufferIteratorCreate: {{{
// Creates a new circularBufferIterator in memory and returns a pointer to it.
//
// @return Pointer to the created circularBufferIterator in memory.
// NULL on allocation. }}}
struct circularBufferIterator *circularBufferIteratorCreate();
// circularBufferIteratorDestroy: {{{
// Destroys the passed circularBufferIterator struct in memory.
//
// @param buf Pointer to the struct circularBufferIterator,
// which is to be destroyed. }}}
void circularBufferIteratorDestroy(struct circularBufferIterator *it);
// circularBufferIteratorPrepare: {{{
// Prepares passed iterator to iterate over the elements of passed buffer.
//
// @param it CircularBufferIterator, which is about to get prepared.
// @param buf CircularBuffer, which should be iterated over by the iterator.
// }}}
void circularBufferIteratorPrepare(struct circularBufferIterator *it, const struct circularBuffer *buf);
// circularBufferIteratorNext: {{{
// Fetches the next element form the passed iterator.
//
// @param it CircularBufferIterator, which the next element should be fetched from.
//
// @return Pointer to the fetched data. May get overwritten by buffer at a later point in time.
// Copying required for use parallel to the buffer.
// }}}
char *circularBufferIteratorNext(struct circularBufferIterator *it);
#endif /* !defined CIRCULAR_BUFFER_H */
circularBuffer.c
#include <string.h> // memcpy
#include <stdlib.h> // SIZE_MAX, calloc, free
#include <stdalign.h> // alignas
#include "circularBuffer.h"
struct circularBuffer {
size_t capacity;
size_t cardinality;
size_t alignElementSize;
size_t originalElementSize;
size_t headOffset;
size_t tailOffset;
alignas(max_align_t) char data[];
};
static inline size_t incrementIndex(const struct circularBuffer *buf, size_t index) {
// Avoid expensive modulo arithmetic
return (++index == buf->capacity) ? 0 : index;
}
struct circularBuffer *circularBufferCreate(size_t capacity, size_t elementSize) {
if (elementSize == 0) {
return NULL;
}
size_t alignElementSize;
if (elementSize >= sizeof(max_align_t)) {
// Least number of blocks sizeof (max_align_t) where one element fits into.
alignElementSize = (elementSize + sizeof (max_align_t) - 1) / sizeof (max_align_t) * sizeof(max_align_t);
} else {
alignElementSize = elementSize;
// Find smallest int x >= 0 where (elementSize + x) divides sizeof (max_align_t) evenly.
while (sizeof(max_align_t) % alignElementSize != 0) { alignElementSize++; }
}
if (SIZE_MAX / alignElementSize <= capacity ||
SIZE_MAX - sizeof (struct circularBuffer) < capacity * elementSize) {
return NULL;
}
// Do I need to take extra care of the initial alignment of buf->data[] at this point?
// Or is the alignas(max_align_t) enough?
struct circularBuffer *buf = calloc(1, sizeof (*buf) + alignElementSize * capacity);
if (!buf) {
return NULL;
}
buf->capacity = capacity;
buf->cardinality = 0;
buf->alignElementSize = alignElementSize;
buf->originalElementSize = elementSize;
buf->headOffset = 0;
buf->tailOffset = 0;
return buf;
}
void circularBufferDestroy(struct circularBuffer *buf) {
free(buf);
}
size_t circularBufferCardinality(const struct circularBuffer *buf) {
return buf->cardinality;
}
int circularBufferIsEmpty(const struct circularBuffer *buf) {
return buf->cardinality == 0;
}
int circularBufferIsFull(const struct circularBuffer *buf) {
return buf->cardinality == buf->capacity;
}
void circularBufferPushHead(struct circularBuffer *buf, void *ptr) {
if (buf->cardinality != 0) {
buf->headOffset = incrementIndex(buf, buf->headOffset);
// Cannot use circularBufferIsFull(buf) at this point,
// since the cardinality isn't incremented yet.
// circularBufferIsFull(buf) uses the cardinality
// to determine fullness.
if (buf->headOffset == buf->tailOffset) {
buf->tailOffset = incrementIndex(buf, buf->tailOffset);
}
}
memcpy(buf->data + buf->headOffset*buf->alignElementSize, ptr, buf->originalElementSize);
if (!circularBufferIsFull(buf)) {
buf->cardinality++;
}
}
char *circularBufferPopTail(struct circularBuffer *buf) {
if (buf->cardinality == 0) {
return NULL;
}
// Store point to be returned.
size_t tmpOffset = buf->tailOffset;;
// Pop internally.
buf->tailOffset = incrementIndex(buf, buf->tailOffset);
buf->cardinality--;
return buf->data + tmpOffset * buf->alignElementSize;
}
const char *circularBufferPeekHead(const struct circularBuffer *buf) {
return buf->cardinality == 0 ? NULL : buf->data + buf->alignElementSize * buf->headOffset;
}
const char *circularBufferPeekTail(const struct circularBuffer *buf) {
return buf->cardinality == 0 ? NULL : buf->data + buf->alignElementSize * buf->tailOffset;
}
struct circularBufferIterator {
struct circularBuffer *buf;
size_t continuousIndex;
size_t actualIndex;
};
struct circularBufferIterator *circularBufferIteratorCreate() {
return calloc(1, sizeof(struct circularBufferIterator));
}
void circularBufferIteratorDestroy(struct circularBufferIterator *it) {
free(it);
}
void circularBufferIteratorPrepare(struct circularBufferIterator *it, const struct circularBuffer *buf) {
it->buf = buf;
it->continuousIndex = 0;
it->actualIndex = it->buf->tailOffset;
}
char *circularBufferIteratorNext(struct circularBufferIterator *it) {
if (it->continuousIndex == it->buf->cardinality) {
return NULL;
}
it->continuousIndex++;
size_t tmp = it->actualIndex;
it->actualIndex = incrementIndex(it->buf, it->actualIndex);
return it->buf->data + tmp * it->buf->alignElementSize;
}
Notes to possible reviewers:
- I'm still unsure about the initial padding of the flexible array member. I'm especially hoping for a review on that topic.
- It's also especially wanted that the old data is overwritten, when new data is pushed to the head and the buffer is already filled - "It's a feature not a bug".
Here is a really small test:
#include <stdio.h>
#include "circularBuffer.h"
void DEBUG_circularBufferPrintContent(struct circularBufferIterator *it, struct circularBuffer *buf);
void DEBUG_circularBufferPrintState(const struct circularBuffer *buf);
int main() {
struct circularBuffer *buf;
struct circularBufferIterator *it;
buf = circularBufferCreate(3, sizeof(long));
it = circularBufferIteratorCreate();
if (!buf) {
printf("ERROR: No buffer was returned.\n");
return -1;
}
if (!it) {
printf("ERROR: No iterator was returned.\n");
return -1;
}
printf("--- TEST INITIAL STATE ---\n");
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
printf("\n--- TEST UNDERFLOW ---\n");
circularBufferPopTail(buf);
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
printf("\n--- TEST PUSH ---\n");
long l = 0;
circularBufferPushHead(buf, &l);
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
printf("\n--- TEST POP ---\n");
circularBufferPopTail(buf);
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
printf("\n--- TEST FULL ---\n");
for (l = 1; l <= 3; l++) { circularBufferPushHead(buf, &l); }
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
printf("\n--- TEST OVERFLOW ---\n");
l = 4;
circularBufferPushHead(buf, &l);
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
printf("\n--- TEST POP ---\n");
circularBufferPopTail(buf);
DEBUG_circularBufferPrintState(buf);
DEBUG_circularBufferPrintContent(it, buf);
circularBufferIteratorDestroy(it);
circularBufferDestroy(but);
return 0;
}
void DEBUG_circularBufferPrintContent(struct circularBufferIterator *it, struct circularBuffer *buf) {
printf("--> DEBUG: ");
long *tmp;
for (circularBufferIteratorPrepare(it, buf); (tmp = (long*) circularBufferIteratorNext(it)) != NULL; ) {
printf("%ld, ", *tmp);
}
printf("\n");
}
void DEBUG_circularBufferPrintState(const struct circularBuffer *buf) {
printf("--> DEBUG: CARDINALITY: %zu, IS_EMPTY: %5s, IS_FULL: %5s\n",
circularBufferCardinality(buf),
circularBufferIsEmpty(buf) ? "true" : "false",
circularBufferIsFull(buf) ? "true" : "false");
}
main.cas well? \$\endgroup\$circulerBuffer. \$\endgroup\$cardinality. \$\endgroup\$