Compare commits

...

8 commits

8 changed files with 215 additions and 26 deletions

View file

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

3
.gitignore vendored
View file

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

View file

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

View file

@ -4,6 +4,7 @@
// #include <string.h> // #include <string.h>
#include "ringbuf.h" #include "ringbuf.h"
#include <assert.h>
#include <stdint.h> #include <stdint.h>
#ifdef DEBUG #ifdef DEBUG
@ -29,7 +30,18 @@ rb_init(struct RingBuf *rb, rb_size_t capacity, ALLOC_T malloc_fn,
void void
rb_destroy(struct RingBuf *rb, FREE_T free_fn) 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 void
@ -46,6 +58,8 @@ rb_push_back(struct RingBuf *rb, const void *item, MEMCPY_T memcpy_fn)
if(rb->count == rb->capacity) if(rb->count == rb->capacity)
return Full; return Full;
assert(rb->buffer != NULL);
memcpy_fn(rb->write_head, item, rb->struct_size); memcpy_fn(rb->write_head, item, rb->struct_size);
// Advance the write head // Advance the write head
@ -117,6 +131,21 @@ rb_pop_front(struct RingBuf *rb, void *item, MEMCPY_T memcpy_fn)
return ReadOk; 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 void
rb_debug_print(struct RingBuf *rb) rb_debug_print(struct RingBuf *rb)
{ {
@ -133,3 +162,5 @@ rb_debug_print(struct RingBuf *rb)
DEBUG_PRINT("============%s\n", ""); 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 * @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 rb The ring buffer
* @param free The free function * @param free The free function
*/ */
void rb_destroy(struct RingBuf *rb, void(free)()); void rb_destroy(struct RingBuf *rb, void(free)());
#ifdef DEBUG
/** /**
* @brief Debug print * @brief Debug print
*/ */
void rb_debug_print(struct RingBuf *rb); 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);
}