What is a Ring/Circular buffer? How to implement in C?
Circular buffer
A circular buffer or circular queue or cyclic buffer or ring buffer is single and fixed size buffer with some programming logic to act as if the elements are connected end-to-end. Circular buffer provides a buffering and queueing mechanism with the help of a fixed size array often used in low memory footprint systems and device drivers. Circular buffer can be implemented with static array or fixed size dynamic array. It never calls any dynamic memory allocation routines like alloc/free/resize during runtime. It manages addition and deletion of queue elements in a circular rotational fashion.
Circular buffer Animation
Implementation of circular buffer
It has a fixed size array to hold elements. It has two members called start and end to point the index in the array to determine the front and the back of the queue. It also has the size member to save element count. At the beginning, we set start and end both to zero index. End will increment as new elements gets added and start will be incremented when an older element gets removed. These increments are circular and never cross beyond max array index. They rounded off to the starting as we do a modulo. Now end may get back to start when the buffer is entirely full. So start and end will point to the same index when the buffer is either full or empty. We often use a count to distinguish the empty and full situation.
Application of circular buffer
Circular buffer is often used in embedded systems having a pair of data producer and data consumer. Keyboard buffer, ethernet frames are some good examples. Keyboard circuler buffer is queued when user presses a key and they are dequeued when application reads it.
Advantages of circular buffer
- Simple logic using array and no complex data structures and logic used
- No dynamic allocation or deallocation and no chance of memory leak and other issues
- Best suit for low memory footprint systems or for device drivers
- Very fast operations and donot include delay of malloc and free
- These can be used in applications as well as low level codes in interrupt handlers and realtime systems
Limitations of circular buffer
- Queue is fixed in size and this cannot be changed.
- addition of new element will overwrite old queue elements or has to wait till some elements are free.
Circular buffer data structure
Circular buffer is a data structure and it has member variables to hold all the context of a circular buffer. Circular buffer data structure should have these member variables
- Buff - The member buff points to an array which is a static array of fixed size or dynamic array of fixed size.
- Start - Start will have the value of the starting index or offset of this queue.
- End - End will have the value of the ending index or offset of this queue.
- Size - Size will have total number of array elements in the buffer. This remains constant throughout the execution.
- Count - Count says the number of available elements in the queue. It increases when an element gets added in the queue. It decreases once an element dequeues from the array.
int * buff;
int start;
int end;
int size;
int count;
} cbuff_t;
typedef struct cbuff_{
int buff[MAX_COUNT];
int start;
int end;
int size;
int count;
} cbuff_t;
Circular buffer initialization
Very often Circular buffer struct variable will be declared in global space. Global variable will ensure all the members are zero.
cbuff_t g_cb;
int g_buff[MAX_COUNT];
cbuff_t * cbuff_new(int size)
{
cbuff_t * cb = &g_cb;
cb->size = size;
cb->buff = g_buff;
return cb;
}
Dynamically allocated data structure should be initialized to zero. Whatever allocated on static or dynamic, size should be set to the number of array elements present on the buffer. Start, end, count should be zero at the initialization.
{
cbuff_t * cb = (cbuff_t*)malloc(sizeof(cbuff_t));
memset(cb, 0, sizeof(cbuff_t));
cb->size = size;
cb->buff = (int*)malloc(sizeof(int)*size);
return cb;
}
Circular buffer cleanup
We can cleanup our circuler buffer object by freeing the array buffer and then freeing the actual data object. Static allocation does not require any cleanup routine.
{
free(cb->buff);
free(cb);
}
Circular buffer add/enqueue
Add or enqueue in the circular buffer happens at the end of the queue. New value will be placed at the location pointed by "end" and "end" will be advanced. End advancement beyond the max size of the array is not possible. Range of "end" is between 0 to (size -1). An overflow condition happens when an "end" is at (size - 1) or count = size. Since the buffer is circuler advancement of end will overlap with start and start has to advance. Moving start to next means the element will be lost. Due to the memory constrain and cyclic nature, circuler buffer will loose the last element in the queue.
{
int end = cb->end;
if(cb->count && (end % cb->size) == cb->start){
printf("Overflow Elem[%d] %d lost\n", cb->start, cb->buff[cb->start]);
cb->start = (cb->start + 1 ) %cb->size;
cb->count --;
}
printf("Added Elem[%d] = %d\n",cb->end, elem);
cb->buff[cb->end] = elem;
cb->end = (cb->end+1) % cb->size;
cb->count ++;
}
Circular buffer remove/dequeue
Remove and dequeue in circular buffer happens when the first element of the queue is removed. Start will advance and count will decrease. Count value zero or start is equal to end is an empty condition in circular buffer. Removal of further elements are not possible and removal process should exit.
{
int start = cb->start ;
int ret = -1;
if(cb->count <= 0) {
printf("Buffer is empty\n");
return ret;
}
if(cb->count || (start % cb->size) != cb->end) {
printf("Removed Elem[%d] = %d\n",cb->start, cb->buff[cb->start]);
ret = cb->buff[cb->start];
cb->start = (cb->start + 1 ) % cb->size;
cb->count--;
} else {
printf("Buffer is empty\n");
}
return ret;
}
Circular buffer locking
Enqueue and dequeue in circular buffer should takecare of the locking of resource. It could happen that dequeue/removal is in process and at the same time enqueue is triggered from an interrupt service routine. Interrupt service routine(ISR) will pause the dequeue routine and modify or might corrupt the members. So add and remove routines should maintain a spin lock or respective locks to ensure member variables are not modified when accessed via another context. There functions with locks implemented will be reentrant and they will look like below. We have a simple demo to add and remove in the buffer and they are not accessed via multithreads so for simplicity we have not added any lock mechanisms.
{
spin_lock();
..
spin_unloack();
}
void cbuff_remove(cbuff_t * cb, int elem)
{
spin_lock();
..
spin_unloack();
}
Circular buffer iteration
Iteration of elements in circular buffer is often required to print the queue or searching any particular element etc. Buffer starts with the position start and ends at at end. Now this start and end may or may not be in increasing order by array index. So the iteration has to round off at the last element and it should move to index zero and continue till count reaches to total counts.
{
int start = cb->start ;
int end = cb->end ;
int i, count = 0;
for(i = start; count < cb->count; i = (i + 1)%cb->size){
printf("Elem[%d] = %d\n", i, cb->buff[i]);
count++;
if(i == (end - 1)) {
break;
}
}
}
Circuler buffer demo code
We are using above function in our demo application. The main application is a menu driven program to add, remove and print the circular buffer.
#include<stdio.h>
#include<malloc.h>
#include<memory.h>
typedef struct cbuff_{
int * buff;
int start;
int end;
int size;
int count;
} cbuff_t;
cbuff_t * cbuff_new(int size)
{
cbuff_t * cb = (cbuff_t*)malloc(sizeof(cbuff_t));
memset(cb, 0, sizeof(cbuff_t));
cb->size = size;
cb->buff = (int*)malloc(sizeof(int)*size);
return cb;
}
void cbuff_add(cbuff_t * cb, int elem)
{
int end = cb->end;
if(cb->count && (end % cb->size) == cb->start){
printf("Overflow Elem[%d] %d lost\n", cb->start, cb->buff[cb->start]);
cb->start = (cb->start + 1 ) %cb->size;
cb->count --;
}
printf("Added Elem[%d] = %d\n",cb->end, elem);
cb->buff[cb->end] = elem;
cb->end = (cb->end+1) % cb->size;
cb->count ++;
}
int cbuff_remove(cbuff_t * cb)
{
int start = cb->start ;
int ret = -1;
if(cb->count <= 0) {
printf("Buffer is empty\n");
return ret;
}
if(cb->count || (start % cb->size) != cb->end) {
printf("Removed Elem[%d] = %d\n",cb->start, cb->buff[cb->start]);
ret = cb->buff[cb->start];
cb->start = (cb->start + 1 ) % cb->size;
cb->count--;
} else {
printf("Buffer is empty\n");
}
return ret;
}
void cbuff_print(cbuff_t * cb)
{
int start = cb->start ;
int end = cb->end ;
int i, count = 0;
for(i = start; count < cb->count; i = (i + 1)%cb->size){
printf("Elem[%d] = %d\n", i, cb->buff[i]);
count++;
if(i == (end - 1)) {
break;
}
}
}
void cbuff_delete(cbuff_t * cb)
{
free(cb->buff);
free(cb);
}
int main(int argc, char *argv[])
{
char key;
int elem;
cbuff_t * cb = cbuff_new(5);
while(1) {
printf("circular buffer add[a], remove[r], print[p] : ");
fflush(stdin);
key = getchar();
switch(key){
case 'a':
{
printf("Element to add : ");
scanf("%d", &elem);
cbuff_add(cb, elem);
break;
}
case 'r':
{
cbuff_remove(cb);
break;
}
case 'p':
{
cbuff_print(cb);
break;
}
default:
{
cbuff_delete(cb);
exit(0);
}
}
}
return 0;
}
Output
The main application can be used to demo add, remove and print of the circular buffer. It can display if buffer is full or buffer is empty.
circular buffer add[a], remove[r], print[p] : r Buffer is empty circular buffer add[a], remove[r], print[p] : a Element to add : 1 Added Elem[0] = 1 circular buffer add[a], remove[r], print[p] : p Elem[0] = 1 circular buffer add[a], remove[r], print[p] : a Element to add : 2 Added Elem[1] = 2 circular buffer add[a], remove[r], print[p] : p Elem[0] = 1 Elem[1] = 2 circular buffer add[a], remove[r], print[p] : a Element to add : 3 Added Elem[2] = 3 circular buffer add[a], remove[r], print[p] : p Elem[0] = 1 Elem[1] = 2 Elem[2] = 3 circular buffer add[a], remove[r], print[p] : a Element to add : 4 Added Elem[3] = 4 circular buffer add[a], remove[r], print[p] : p Elem[0] = 1 Elem[1] = 2 Elem[2] = 3 Elem[3] = 4 circular buffer add[a], remove[r], print[p] : a Element to add : 5 Added Elem[4] = 5 circular buffer add[a], remove[r], print[p] : p Elem[0] = 1 Elem[1] = 2 Elem[2] = 3 Elem[3] = 4 Elem[4] = 5 circular buffer add[a], remove[r], print[p] : a Element to add : 6 Overflow Elem[0] 1 lost Added Elem[0] = 6 circular buffer add[a], remove[r], print[p] : p Elem[1] = 2 Elem[2] = 3 Elem[3] = 4 Elem[4] = 5 Elem[0] = 6 circular buffer add[a], remove[r], print[p] : r Removed Elem[1] = 2 circular buffer add[a], remove[r], print[p] : p Elem[2] = 3 Elem[3] = 4 Elem[4] = 5 Elem[0] = 6 circular buffer add[a], remove[r], print[p] : r Removed Elem[2] = 3 circular buffer add[a], remove[r], print[p] : p Elem[3] = 4 Elem[4] = 5 Elem[0] = 6 circular buffer add[a], remove[r], print[p] : r Removed Elem[3] = 4 circular buffer add[a], remove[r], print[p] : p Elem[4] = 5 Elem[0] = 6 circular buffer add[a], remove[r], print[p] : r Removed Elem[4] = 5 circular buffer add[a], remove[r], print[p] : p Elem[0] = 6 circular buffer add[a], remove[r], print[p] : r Removed Elem[0] = 6 circular buffer add[a], remove[r], print[p] : p circular buffer add[a], remove[r], print[p] : r Buffer is empty circular buffer add[a], remove[r], print[p] :
About our authors: Team EQA
You have viewed 1 page out of 248. Your C learning is 0.00% complete. Login to check your learning progress.
copyright
https://www.equestionanswers.com/c/c-circular-buffer.php