A circular buffer stores a fixed number of items in a wrap-around array using two indices: head (next read) and tail (next write). Push wraps at capacity, and full/empty are told apart by tracking count (or a bit).
Same storage, laid flat. H = head (next pop), T = tail (next push).
typedef struct {
int *buf;
size_t cap, head, tail, size;
} rb_t;
bool rb_push(rb_t *r, int v) {
if (r->size == r->cap) return false; /* or overwrite: rb_pop() */
r->buf[r->tail] = v;
r->tail = (r->tail + 1) % r->cap;
r->size++;
return true;
}
bool rb_pop(rb_t *r, int *out) {
if (r->size == 0) return false;
*out = r->buf[r->head];
r->head = (r->head + 1) % r->cap;
r->size--;
return true;
}
Fixed-capacity FIFOs show up in producer/consumer pipelines, embedded serial I/O, ring loggers ("last N events"), lock-free SPSC queues, and audio/DSP block processing. The wrap-around trick means no data copying on pop and no heap allocations at runtime.
The classic gotcha: distinguishing full from empty when head == tail. Fixes: keep a separate size, keep one slot unused (waste 1 of N), or use unmasked read/write indices modulo 2·cap.