Compare commits

...

8 commits

8 changed files with 215 additions and 26 deletions

View file

@ -51,7 +51,7 @@ AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: AllDefinitions
AlwaysBreakBeforeMultilineStrings: false

3
.gitignore vendored
View file

@ -4,3 +4,6 @@
*.a
build
driver
*.elf
*.json
.cache

View file

@ -1,19 +1,21 @@
# SPDX-License-Identifier: MIT
CC = gcc
CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
CFLAGS += -Wno-unused-variable -Wno-unused-function
CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
CFLAGS += -Wno-unused-variable -Wno-unused-function
CFLAGS += -Wno-unused-but-set-variable -Wno-unused-value -Wno-unused-label
CFLAGS += -Wno-unused-result -Wno-unused-const-variable
ifneq ($(DEBUG), 1)
CFLAGS += -I.
ifneq ($(RELEASE), 1)
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
CFLAGS += -DDEBUG
else
CFLAGS += -fno-exceptions -fno-asynchronous-unwind-tables -fno-ident
CFLAGS += -fno-unwind-tables -fno-stack-protector -fno-plt -fno-pic
CFLAGS += -O3 -std=c99 -march=native -mtune=native -fomit-frame-pointer
CFLAGS += -fshort-enums
else
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
CFLAGS += -DDEBUG
endif # DEBUG
LDFLAGS = -lm
@ -25,6 +27,9 @@ ASMS = $(C_SOURCES:.c=.s)
all: $(OBJECTS)
test: test/test.elf
./test/test.elf
%.o: %.c $(C_HEADERS)
@echo "CC $<"
@$(CC) $(CFLAGS) -c $< -o $@
@ -39,6 +44,9 @@ driver: $(OBJECTS)
run: driver
@./driver
test/test.elf: test/test_ringbuf.o ringbuf.o
@$(CC) $(CFLAGS) $^ -o $@ -lpthread -lcmocka
lib: $(OBJECTS)
@ar rcs librbuf.a ringbuf.o
@ -50,7 +58,7 @@ install:
@cp ringbuf.h /usr/local/include
clean:
rm -f $(OBJECTS) $(ASMS) driver librbuf.a librbuf.so
rm -f $(OBJECTS) $(ASMS) driver librbuf.a librbuf.so **/*.o **/*.elf
asm: $(ASMS) $(OBJECTS)
wc -l $(ASMS)
@ -62,4 +70,4 @@ tidy:
format:
clang-format -i $(C_SOURCES) $(C_HEADERS)
.PHONY: all clean format asm
.PHONY: all clean format asm test

44
README.md Normal file
View file

@ -0,0 +1,44 @@
# RingBuf
RingBuf is an allocator-agnostic, non-overwriting circular/ring buffer
implementation in C99.
See: [Circular Buffer (Wikipedia)](https://en.wikipedia.org/wiki/Circular_buffer)
## Features
- Space Efficiency
The code is designed to be portable and flexible. The inspiration initially
came to me when designing a network driver. When operating in memory
constrained environments, every byte is of upmost importance. Traditional
metaprogramming such as templating in C++ and template metaprogramming in C,
although generic has the side effect of expanding into discrete machine code
specific for the specialization applied, essentially scaling (spatially) linear
with the amount of different datatypes we specialize on. This implementation
circumvents this by treating all data as a void pointer with a length. This
implies **no deep copies**.
- Allocator Agnostic
Another common characteristic of memory constrained environments are
custom malloc implementations and/or variations.
- Signatures
- Arena
## Design considerations
- Holding a reference to malloc internally would make a tidier interface
- Passing a user-defined function for deep-copies would enable certain applications
## Usage
In essence:
```c
int value = 42;
struct RingBuf rb;
rb_init(&rb, 10, malloc, sizeof(int));
rb_push_back(&rb, (void *)&data, memcpy);
rb_pop_front(&rb, &value, memcpy);
```
Most of these functions return Enum result types. See:
[ringbuf.h](./ringbuf.h).
## Future Improvements
- Trim the code
- Reduce boilerplate in tests
- Reduce the number of tests in exchange for better test fit

View file

@ -4,27 +4,16 @@
#include <string.h>
#include <stdio.h>
#define rb_size_t size_t
#ifndef DEBUG
#define DEBUG
#endif
// #define rb_size_t size_t
// #define rb_size_t int
#include "ringbuf.h"
typedef int DATATYPE;
/**
* @brief Debug print and empty the ringbuf
*/
void
rb_debug_empty(struct RingBuf *rb)
{
int d;
if(rb->count == 0)
return;
printf("Debug Data: [");
while(rb_pop_front(rb, &d, memcpy) == ReadOk)
printf("%d,", d);
printf("\b]\n");
}
int
main(void)
{

View file

@ -4,6 +4,7 @@
// #include <string.h>
#include "ringbuf.h"
#include <assert.h>
#include <stdint.h>
#ifdef DEBUG
@ -29,7 +30,18 @@ rb_init(struct RingBuf *rb, rb_size_t capacity, ALLOC_T malloc_fn,
void
rb_destroy(struct RingBuf *rb, FREE_T free_fn)
{
free_fn(rb->buffer);
if(rb->buffer) { // Prevent double-free
free_fn(rb->buffer);
rb->buffer = NULL;
rb->buffer_end = NULL;
rb->struct_size = 0;
rb->capacity = 0;
rb->count = 0;
rb->write_head = NULL;
rb->read_head = NULL;
}
}
void
@ -46,6 +58,8 @@ rb_push_back(struct RingBuf *rb, const void *item, MEMCPY_T memcpy_fn)
if(rb->count == rb->capacity)
return Full;
assert(rb->buffer != NULL);
memcpy_fn(rb->write_head, item, rb->struct_size);
// Advance the write head
@ -117,6 +131,21 @@ rb_pop_front(struct RingBuf *rb, void *item, MEMCPY_T memcpy_fn)
return ReadOk;
}
#ifdef DEBUG
#include <stdio.h>
void
rb_debug_empty(struct RingBuf *rb)
{
int d;
if(rb->count == 0)
return;
printf("Debug Data: [");
while(rb_pop_front(rb, &d, memcpy) == ReadOk)
printf("%d,", d);
printf("\b]\n");
}
void
rb_debug_print(struct RingBuf *rb)
{
@ -133,3 +162,5 @@ rb_debug_print(struct RingBuf *rb)
DEBUG_PRINT("============%s\n", "");
}
#endif

View file

@ -111,12 +111,25 @@ enum ReadResult rb_pop_front(struct RingBuf *rb, void *item,
/**
* @brief Free the ring buffer
*
* @details This function is idempotent, consecutive calls will not result in a
* double free.
*
* @param rb The ring buffer
* @param free The free function
*/
void rb_destroy(struct RingBuf *rb, void(free)());
#ifdef DEBUG
/**
* @brief Debug print
*/
void rb_debug_print(struct RingBuf *rb);
/**
* @brief Debug print and empty the ringbuf
*/
void rb_debug_empty(struct RingBuf *rb);
#endif

101
test/test_ringbuf.c Normal file
View file

@ -0,0 +1,101 @@
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <cmocka.h>
#include <stdio.h>
#include <stdlib.h>
#include "ringbuf.h"
/** Tests initialization */
static void
test_rb_init_empty(void **state) {
struct RingBuf rb;
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 0);
}
/** Tests push_back */
static void
test_rb_push_back(void **state) {
struct RingBuf rb;
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
int data = 10;
enum WriteResult wr = rb_push_back(&rb, (void *)&data, memcpy);
assert_int_equal(wr, WriteOk);
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 1);
}
/** Tests the destroy function */
static void
test_rb_init_destroy(void **state) {
struct RingBuf rb = { 0, 0, 0, NULL, NULL, NULL, NULL };
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
int data = 10;
rb_push_back(&rb, (void *)&data, memcpy);
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 1);
rb_destroy(&rb, free);
assert_null(rb.buffer);
}
/** Tests fill, but not overflow/wrap-around */
static void
test_rb_push_back_fill(void **state) {
struct RingBuf rb = { 0, 0, 0, NULL, NULL, NULL, NULL };
rb_size_t capacity = 10;
rb_size_t struct_size = sizeof(int);
rb_init(&rb, capacity, malloc, struct_size);
const int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Insert them all sequentially
for(int i = 0; rb_push_back(&rb, &arr[i], memcpy) == WriteOk; i++);
assert_int_equal(rb.count, 10);
assert_int_equal(rb.capacity, capacity);
// Read them out and assert expected value
for(int i = 0, d = __INT_MAX__; rb_pop_front(&rb, &d, memcpy) == ReadOk;
i++) {
assert_int_equal(d, arr[i]);
}
assert_int_equal(rb.capacity, capacity);
assert_int_equal(rb.count, 0);
rb_destroy(&rb, free);
assert_null(rb.buffer);
}
int
main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_rb_init_empty),
cmocka_unit_test(test_rb_push_back),
cmocka_unit_test(test_rb_init_destroy),
cmocka_unit_test(test_rb_push_back_fill),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}