I like your diagrams in the comments - a picture really can express much more than words sometimes! It's a shame the text lines are so long - I recommend keeping line lengths less than a standard terminal width of 80 columns (even in these days of large monitors, most readers prefer to have more files visible side-by-side than to have longer lines in each).
struct Queue {
size_t capacity, size;
void** data;
size_t head, tail;
};
Good choice of type for capacity
and size
. I'd probably have head
and tail
be pointers to void*
rather than indexes. Instead of maintaining size
as a member, we could compute it when needed from head
and tail
, provided we don't ever completely fill the queue (i.e. expand it just before head==tail
, rather than just after).
Queue* queue = calloc(1, sizeof *queue);
assert(queue);
That's plain wrong. We know that calloc()
can return zero, so claiming queue
is non-zero is mistaken.
It seems that you think assert()
is a tool for run-time checks, but that is not the case. assert()
exists to document things we know to be true (and, in debug builds, let us know when our claims are wrong).
The correct code is
Queue* queue = calloc(1, sizeof *queue);
if (!queue) { return queue; }
Not only does this perform the check in non-debug builds, it reports the failure to the caller, who can handle it appropriately.
Such misuse of assert()
exists throughout the program.
It's not clear why we're using calloc()
here rather than malloc()
- we write all the storage we allocate, so the zero-initialising done by calloc()
is just a waste of cycles.
void queue_free(Queue* queue) {
assert(queue);
free(queue->data);
Why not just handle a null queue
, to give an interface consistent with free()
? I'd write
void queue_free(Queue* queue)
{
if (!queue) { return; }
free(queue->data);
free(queue);
}
That makes life much easier for callers, who can now pass their Queue*
to queue_free()
without needing a separate path for null pointers.
if (queue->head > queue->tail) {
for (size_t i = queue->head; i < queue->capacity; ++i) {
tmp[i + queue->capacity] = tmp[i];
tmp[i] = NULL;
}
queue->head += queue->capacity;
}
I think the loop there can be replaced by a simple memmove()
. There should be no need to assign NULL
to the positions between tail
and head
, as we'll not access those entries before they are next written.
queue_iterate
has:
if (queue->size == 0) {
return;
}
That's unnecessary, since the rest of the function is a for
loop that will do nothing when size is zero. We can just omit this test.