What would be the preferred C way of implementing a simple generic ring-buffer.
Which approach from the 2 below (or even some 3rd) would you use and why?
Specifically, this will be part of an embedded automotive application. I am looking for an approach that is the easiest to understand and maintain. Performance is not a deciding factor.
Option 1, having common structure and using void pointers:
typedef struct ring_buffer
{
void *start_of_buffer; // inclusive
void *end_of_buffer; // exclusive
size_t element_size;
void *head;
void *tail;
size_t count;
} ring_buffer_t;
void * next_pointer(ring_buffer_t *handle, void *pointer)
{
void *next = (char *)pointer + handle->element_size;
if (next == handle->end_of_buffer)
next = handle->start_of_buffer;
return next;
}
void push(ring_buffer_t *handle, void *element)
{
memcpy(handle->head, element, handle->element_size);
handle->head = next_pointer(handle, handle->head);
handle-count++;
}
void pop(ring_buffer_t *handle, void *element)
{
memcpy(element, handle->tail, handle->element_size);
handle->tail = next_pointer(handle, handle->tail);
handle->count--;
}
Option 2, generating type specific structure and using macros:
#define ring_buffer_t(type)
struct
{
type *start_of_buffer;
type *end_of_buffer;
type *head;
type *tail;
size_t count;
}
#define NEXT_POINTER(handle, pointer)
(pointer == (handle.end_of_buffer - 1)) ? start_of_buffer : pointer + 1
#define PUSH(handle, element)
do
{
*handle.head = element;
handle.head = NEXT_POINTER(handle, handle.head);
handle.count++;
} while (0)
#define POP(handle, element)
do
{
element = *handle.tail;
handle.tail = NEXT_POINTER(handle, handle.tail);
handle.count--;
} while (0)
I am leaning more towards option 2 because:
- this is a simple data structure with very simple API and not much logic in push and pop functions
- option 1 includes some overhead having to store element size and then increment pointers based on this size
- using clear variable assignment instead of memcpy() as in option 1
- type-safe pushing and popping as opposed to option 1
What I dislike about option 2:
- macros complicating maintainability and possibly making it hard for new developers to understand (although in this case I think it is pretty straightforward)
- macros making it more difficult to debug (although not much to debug)
- multiple statements in push and pop macros (although only 3)
7