I've tried implementing a ring buffer/cyclical queue in C.
It is supposed to take all of the arguments via argv, push them onto the queue one by one, and then pop them off of the queue in the same fashion, printing them on the way out.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>
struct buffer
{
int32_t front, rear, capacity, *array;
};
__attribute__ ((noreturn)) void prog_error(const char* str)
{
perror(str);
exit(1);
}
struct buffer *init_queue(int32_t size)
{
struct buffer *queue = malloc(sizeof(struct buffer));
if (!queue)
return NULL;
queue->capacity = size;
queue->front = -1;
queue->rear = -1;
queue->array = malloc(queue->capacity * sizeof(int32_t));
if (!queue->array)
return NULL;
return queue;
}
void enqueue(struct buffer *queue, int32_t x)
{
if (((queue->rear + 1) % queue->capacity == queue->rear))
prog_error("Queue overflow");
queue->rear = (queue->rear + 1) % queue->capacity;
queue->array[queue->rear] = x;
if (queue->front == -1)
queue->front = queue->rear;
}
int32_t dequeue(struct buffer *queue)
{
int32_t data = 0;
if (queue->front == -1)
prog_error("Queue underflow");
data = queue->array[queue->front];
if (queue->front == queue->rear)
queue->front = queue->rear = -1;
queue->front = (queue->front + 1) % queue->capacity;
return data;
}
int main(int argc, char **argv)
{
if (argc < 2)
prog_error("Too few arguments");
int32_t size = (int32_t) argc - 1;
struct buffer *queue;
if (!(queue = init_queue(size)))
prog_error("Allocation error");
for (int32_t i = 1; i < size; ++i)
enqueue(queue, (int32_t) atoi(argv[i]));
for (int32_t i = 0; i < size; ++i)
printf("%" PRId32 "\n", dequeue(queue));
free(queue);
}
But the last value is always replaced by a 1.
And also, if I give it exactly 1 value then it underflows (or is that normal behavior for a ring buffer?). How can I fix this?
I think you've got some of your indexing wrong. In your implementation:
When you enqueue, you should do the steps in the following order:
rear
;rear
, possibly wrapping around.You increment the rear first, then store the value and then adjust the front. You also have a one-off error when you check for overflow.Here's the corrected version:
void enqueue(struct buffer *queue, int32_t x)
{
if (queue->rear >= 0 && (queue->rear) % queue->capacity == queue->front)
prog_error("Queue overflow");
if (queue->front == -1)
queue->front = queue->rear = 0;
queue->array[queue->rear] = x;
queue->rear = (queue->rear + 1) % queue->capacity;
}
Similarly for dequeueing:
You have the last two points mixed up. (The queue can only be empty after removing an item.) So:
int32_t dequeue(struct buffer *queue)
{
int32_t data = 0;
if (queue->front == -1)
prog_error("Queue underflow");
data = queue->array[queue->front];
queue->front = (queue->front + 1) % queue->capacity;
if (queue->front == queue->rear)
queue->front = queue->rear = -1;
return data;
}
Your use of the queue is awkward. Usually, stuff is enqueued somewhere and dequeued somewhere else. The code that dequeues usually doesn't know how many items are in the queue. Therefore, a queue has a means of telling whether it is empty or not. Dequeueing an empty queue leads to underflow.
You could check (queue->front
directly, but here's a wrapper function for this:
int isempty(struct buffer *queue)
{
return (queue->front < 0);
}
This leads to client code like so:
int main(int argc, char **argv)
{
if (argc < 2)
prog_error("Too few arguments");
int32_t size = (int32_t) argc - 1;
struct buffer *queue = init_queue(size);
if (queue == NULL)
prog_error("Allocation error");
for (int32_t i = 0; i < size; ++i)
enqueue(queue, (int32_t) atoi(argv[i + 1]));
while (!isempty(queue))
printf("%" PRId32 "\n", dequeue(queue));
free(queue);
}
Finally, he business with the −1s leads to some code clutter. Maybe the queue is better represented as front
and length
:
struct buffer
{
int32_t front;
int32_t length;
int32_t capacity;
int32_t *array;
};
struct buffer *init_queue(int32_t size)
{
struct buffer *queue = malloc(sizeof(struct buffer));
if (!queue) return NULL;
queue->capacity = size;
queue->front = 0;
queue->length = 0;
queue->array = malloc(queue->capacity * sizeof(*queue->array));
if (!queue->array) return NULL;
return queue;
}
int isempty(struct buffer *queue)
{
return (queue->length == 0);
}
void enqueue(struct buffer *queue, int32_t x)
{
if (queue->length == queue->capacity) prog_error("Queue overflow");
queue->array[queue->front + queue->length++] = x;
}
int32_t dequeue(struct buffer *queue)
{
int32_t data = 0;
if (queue->length == 0) prog_error("Queue underflow");
data = queue->array[queue->front++];
if (queue->front > queue->capacity) queue->front = 0;
queue->length--;
return data;
}
And, but then I'll stop :)
, you should not only free the memory for the queue struct itself, but also for the array
member. It's a good idea to create a queue_destroy
function for this.