Circular buffer using arrays in c


  • Creating a Circular Buffer in C and C++
  • Qt Documentation
  • Circular array
  • Circular Queue implementation in C
  • Circular Queue | Set 1 (Introduction and Array Implementation)
  • Ring buffer basics
  • Creating a Circular Buffer in C and C++

    August 7, Ken Wada The ring buffer's first-in first-out data structure is useful tool for transmitting data between asynchronous processes. The ring buffer is a circular software queue. This queue has a first-in-first-out FIFO data characteristic. These buffers are quite common and are found in many embedded systems. Usually, most developers write these constructs from scratch on an as-needed basis.

    This library enables the developer to create the queue and other lists relatively easily. The ring buffer usually has two indices to the elements within the buffer. The distance between the indices can range from zero 0 to the total number of elements within the buffer. The use of the dual indices means the queue length can shrink to zero, empty , to the total number of elements, full. Figure 1 shows the ring structure of the ring buffer, FIFO queue.

    Figure 1: Structure of a ring buffer. The data gets PUT at the head index, and the data is read from the tail index. The oldest data gets retrieved from the tail index. Figure 2 shows how the head and tail index varies in time using a linear array of elements for the buffer. Figure 2: Linear buffer implementation of the ring buffer.

    Use cases Single process to single process In general, the queue is used to serialize data from one process to another process. The serialization allows some elasticity in time between the processes. In many cases, the queue is used as a data buffer in some hardware interrupt service routine. This buffer will collect the data so that at some later time another process can fetch the data for further processing. This use case is the single process to process buffering case.

    This use case is typically found as an interface between some very high priority hardware service buffering data to some lower priority service running in some background loop.

    This simple buffering use case is shown in Figure 3. Figure 3: A single process to process buffer use case In many cases, there will be a need for two queues for a single interrupt service. Multiple processes to single process A little less common is the requirement to serialize many data streams into one receiving streams.

    These use cases are quite common in multi-threaded operating systems. In this case, there are many client threads requesting some type of serialization from some server or broker thread. The requests or messages are serialized into a single queue which is received by a single process. Figure 4 shows this use case. Figure 4: Multiple processes to process use case. Single process to multiple processes The least common use case is the single process to multiple processes case.

    The difficulty here is to determine where to steer the output in real time. Usually, this is done by tagging the data elements in such a way that a broker can steer the data in some meaningful way. Figure 5 shows the single process to multiple processes use case.

    Since queues can be readily created, it is usually better to create multiple queues to solve this use case than it would be to use a single queue.

    Figure 5: Single process to multiple processes use case. Figure 6 shows how to reorganize the single process to multiple process use case using a set of cascaded queues. Figure 6: Single process to multiple process use case using a dispatcher and multiple queues. Managing overflow One must be able to handle the case where the queue is full and there isstill incoming data. This case is known as the overflow condition.

    There are two methods which handle this case. They are to drop thelatest data or to overwrite the oldest data. Either style may beimplemented. In our case, I will use the drop latest incoming datamethod. Design features In this very specific software queue implementation, I shall use theKISS principle to implement a very simple ring buffer queue. The basicpurpose here is to create a queue which can handle a stream of bytesinto a fixed buffer for the single process to single process use case.

    This type of ring buffer is very handy to have for a simple bufferedserial device. The design features for this simple queue is as follows: 1. The buffer will contain a fixed number of bytes. This number of bytes will be set by a macro definition in the header file. The overflow condition will be managed via the drop latestinformation process.

    This means in the event of an overflow, the latestincoming data will be dropped. Given these features leads us to our first listing. Again, the 1stlisting Listing 1 is the main ring buffer header file. This filedefines all the interfaces and attributes required for our very simplering buffer implementation.

    Listing 1. The interfaces and attributes for a simple ring buffer object. Thefollowing is a list of what is contained within the ringBufS record.

    In our case, we are managing a byte buffer ofunsigned characters. The incoming bytes get written to the managedbuffer using this index. The arithmetic for this index is in modulo Of course, if wedefined a different value for the buffer size then the modulusarithmetic will change accordingly.

    This indexalso follows the same modulus arithmetic as in the HEAD index case. The simple ring buffer methods There are six 6 methods for the simple unsigned character ring buffer. A brief description of these methods is shown in Table 1. Table 1: The set of ring buffer queue methods. In this case, we simply clear all of the objectmemory. This not only flushes the queue. It also clears the buffer too.

    Listing 2: initializing the simple ring buffer. In this case we just check to see whether or not the count fieldis zero. Listing 3. Testing the queue empty condition. Listing 4 shows that this is simply a check of the count field against the buffersize. Listing 4. Testing the queue full condition. Notice that the returnvalue in this method is an integer.

    We use the value of -1 to signalthat an attempt was made to retrieve a value from an empty queue. This method encapsulates the modulus arithmetic required for updating the buffer indices. Listing 5. Getting a byte from the queue. This listing shows the incoming byte is dropped if the queue is full.

    Listing 6. Placing the data into the queue. In this case we set the countand the indices to zero. We can optionally clear the buffer to zero 0 if required. In some cases this may be useful for diagnostics purposes. Listing 7. Flushing the queue. Also, I mentioned some of the use cases for the ring buffer. Thissimple ring buffer can be used in a wide variety of simple serial deviceinterfaces.

    In fact, I have used this simple construct many times inseveral projects. The next step is to go over this simple case and to analyze the coredesign pattern within this simple ring buffer implementation. We canextend this simple pattern to give us a construct that has a bit morecapability and can be used in more complex systems. We can extend thissimple design pattern to accommodate the following: 1.

    Queuing up different data types 2. Varying the user buffer and user buffer size 3. Reuse the core design pattern in a type-safe way Extending this simple ring buffer design to a more reusable and extensible design will be a topic for a future article.

    To access the code in this article, download the rinBufS. Ken Wada is president and owner of AuriumTechnologies, an independent product design and consulting firm inCalifornia's Silicon Valley.

    Qt Documentation

    Circular buffers are often used as fixed-sized queues. The fixed size is beneficial for embedded systems, as developers often try to use static data storage methods rather than dynamic allocations. Circular buffers are also useful structures for situations where data production and consumption happen at different rates: the most recent data is always available. If the consumer cannot keep up with production, the stale data will be overwritten with more recent data.

    By using a circular buffer, we can ensure that we are always consuming the most recent data. For additional use cases, check out Ring Buffer Basics on Embedded. C Implementation We will start with a C implementation, as this exposes us to some of the design challenges and tradeoffs when creating a circular buffer library. Using Encapsulation Since we are creating a circular buffer library, we want to make sure users work with our library APIs instead of modifying the structure directly.

    We also want to keep the implementation contained within our library so we can change it as needed, without requiring end users to update their code. We will create a handle type that they can use instead. This will prevent us from needing to cast the pointer within our function implementation. Inside of our interface, we would handle the translation to the appropriate pointer type.

    We keep the circular buffer type hidden from users, and the only way to interact with the data is through the handle. API Design First, we should think about how users will interact with a circular buffer: They need to initialize the circular buffer container with a buffer and size They need to destroy a circular buffer container They need to reset the circular buffer container They need to be able to add data to the buffer They need to be able to get the next value from the buffer They need to know whether the buffer is full or empty They need to know the current number of elements in the buffer They need to know the max capacity of the buffer Using this list, we can put together an API for our library.

    Users will interact with the circular buffer library using our opaque handle type, which is created during initialization. You can use any particular type that you like — just be careful to handle the underlying buffer and number of bytes appropriately. The queue is thread-safe because the producer will only modify the head index, and the consumer will only modify the tail index.

    While either index might be slightly out-of-date in a given context, this will not impact the thread safety of the queue. Using the full flag, however, creates a requirement for mutual exclusion. This is because the full flag is shared by both the producer and consumer. Of course, the decision has its tradeoffs. If your buffer element has a large memory footprint such as a buffer that is sized for a camera i-frame , wasting a slot may not be reasonable on your system.

    If you do not have mutual exclusion available e. The memory model used on your system may also have an impact on your decision to go without a lock. The implementation below uses the bool flag. Using the flag requires additional logic in the get and put routines to update the flag.

    We use the container structure for managing the state of the buffer. To preserve encapsulation , the container structure is defined inside of our library. Implementation One important detail to note is that each of our APIs requires an initialized buffer handle.

    If the interfaces are improperly used, the program will fail immediately rather than requiring the user to check and handle the error code. Abort trap: 6 Another important note is that the implementation shown below is not thread-safe. No locks have been added to the underlying circular buffer library. Our API has clients provide the underlying buffer and buffer size, and we return a circular buffer handle to them.

    The reason we want our users to provide the buffer is that this allows for a statically allocated buffer. If our API created the buffer under the hood, we would need to make use of dynamic memory allocation, which is often disallowed in embedded systems programs.

    We are required to provide a circular buffer structure instance within the library so that we can return a pointer to the user. I have used malloc for simplicity. Systems which cannot use dynamic memory simply need to modify the init function to use a different method, such as allocation from a static pool of pre-allocated circular buffer structures. Another approach would be to break encapsulation, allowing users to statically declare circular buffer container structures outside of the library.

    We could also have our init function create a container structure on the stack and return it wholesale. However, since encapsulation is broken, users will be able to modify the structure without using the library routines.

    If you want to preserve encapsulation, you need to work with pointers instead of concrete structure instances. Before we return from init, we ensure that the buffer container has been created in an empty state. In this case, we call free on our container.

    We do not attempt to free the underlying buffer, since we do not own it. Many proposed size calculations use modulo, but I ran into strange corner cases when testing that out. I opted for a simplified calculation using conditional statements. If the buffer is full, we know that our capacity is at the maximum. If head is greater-than-or-equal-to the tail, we simply subtract the two values to get our size.

    If tail is greater than head, we need to offset the difference with max to get the correct size. Adding and removing data from a circular buffer requires manipulation of the head and tail pointers.

    When adding data to the buffer, we insert the new value at the current head location, then we advance head. When we remove data from the buffer, we retrieve the value of the current tail pointer and then advance tail. Adding data to the buffer requires a bit more thought, however. If the buffer is full, we need to advance our tail pointer as well as head.

    We also need to check whether inserting a value triggers the full condition. If our buffer is already full, we advance tail. We always advance head by one. Modulo will cause the head and tail values to reset to 0 when the maximum size is reached. This ensures that head and tail are always valid indices of the underlying data buffer. Instead, we can use conditional logic to reduce the total number of instructions. When we remove a value, the full flag is set to false, and the tail pointer is advanced.

    The first version inserts a value into the buffer and advances the pointer. If the buffer is full, the oldest value will be overwritten.

    This is provided for demonstration purposes, but we do not use this variant in our systems. If the buffer is empty we do not return a value or modify the pointer. Instead, we return an error to the user.

    Our APIs are going to be similar to the C implementation. Our class will provide interfaces for: Resetting the buffer to empty Adding data.

    Circular array

    If the head is one greater than tail we can say that array is full. Because, if the array is full then the tail must be pointing to the previous element of the element which head is pointing to.

    Initially, the value of both the head and tail is To insert an element into the array we use this function. It inserts a new element at the end of the buffer i. It takes input from the user, increments the tail value by 1 and inserts the new element there.

    The deletion is done at the head position. This function stores the deleted element in a temporary variable and increments the head value by 1. The elements are displayed from head position to tail position. Because, if the tail is pointing to the last index of the array then we have to increment the value of tail and set it to 0 while insertion.

    It has a fixed size. So, in the program, we consider the size of the buffer as 3. The ring buffer implemented in the program stores integer data. The main function in the program creates a menu-driven code. Case 1 — To insert We have to check whether the buffer has an empty space. Case 2 — To delete To delete an element from the buffer, the buffer must contain at least one element. Then we call the del function to delete an element. Case 3 — To display Here we display the values stored in the buffer sequentially from head position to tail position using display function.

    Case 4 — To exit If the user wants to exit from the program we terminate the loop using a condition. Iteration 2 — We insert a number 2 into the buffer. Iteration 3 — We insert a number 3 into the buffer.

    Circular Queue implementation in C

    Iteration 4 — We try to insert a number. To preserve encapsulationthe container structure is defined inside of our library. Implementation One important detail to note is that each of our APIs requires an initialized buffer handle.

    If the interfaces are improperly used, the program will fail immediately rather than requiring the user to check and handle the error code. Abort trap: 6 Another important note is that the implementation shown below is not thread-safe. No locks have been added to the underlying circular buffer library. Our API has clients provide the underlying buffer and buffer size, and we return a circular buffer handle to them.

    The reason we want our users to provide the buffer is that this allows for a statically allocated buffer. If our API created the buffer under the hood, we would need to make use of dynamic memory allocation, which is often disallowed in embedded systems programs.

    Circular Queue | Set 1 (Introduction and Array Implementation)

    We are required to provide a circular buffer structure instance within the library so that we can return a pointer to the user. I have used malloc for simplicity. Systems which cannot use dynamic memory simply need to modify the init function to use a different method, such as allocation from a static pool of pre-allocated circular buffer structures. Another approach would be to break encapsulation, allowing users to statically declare circular buffer container structures outside of the library.

    We could also have our init function create a container structure on the stack and return it wholesale. However, since encapsulation is broken, users will be able to modify the structure without using the library routines. If you want to preserve encapsulation, you need to work with pointers instead of concrete structure instances. Before we return from init, we ensure that the buffer container has been created in an empty state. In this case, we call free on our container.

    We do not attempt to free the underlying buffer, since we do not own it. Many proposed size calculations use modulo, but I ran into strange corner cases when testing that out. I opted for a simplified calculation using conditional statements. If the buffer is full, we know that our capacity is at the maximum. If head is greater-than-or-equal-to the tail, we simply subtract the two values to get our size.

    If tail is greater than head, we need to offset the difference with max to get the correct size. Adding and removing data from a circular buffer requires manipulation of the head and tail pointers. When adding data to the buffer, we insert the new value at the current head location, then we advance head.

    When we remove data from the buffer, we retrieve the value of the current tail pointer and then advance tail. Adding data to the buffer requires a bit more thought, however.

    Ring buffer basics

    If the buffer is full, we need to advance our tail pointer as well as head. We also need to check whether inserting a value triggers the full condition.

    If our buffer is already full, we advance tail. We always advance head by one. Modulo will cause the head and tail values to reset to 0 when the maximum size is reached.

    This ensures that head and tail are always valid indices of the underlying data buffer. Instead, we can use conditional logic to reduce the total number of instructions.


    thoughts on “Circular buffer using arrays in c

    Leave a Reply

    Your email address will not be published. Required fields are marked *