Dynamically sized queue This is my first deep dive into C, and I want to make sure that I'm not doing anything stupid. The code below creates an "Array List" of sorts, which automatically resizes itself.

This code is meant to run on a PIC24, where memory is at a premium.

#include &lt;string.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;stdint.h&gt;

#ifndef CHAR_QUEUE_H
#define CHAR_QUEUE_H
#define QUEUE_INIT_SIZE 128

typedef struct CharQueue_t{
    uint16_t size;
    uint16_t capacity;
    unsigned char *data;
    void (*append) (struct CharQueue_t *, unsigned char);
    void (*destroy) (struct CharQueue_t *);
    unsigned char (*get) (struct CharQueue_t *, uint16_t);
    unsigned char (*next) (struct CharQueue_t *);
} CharQueue_t;

CharQueue_t *CharQueueInit();

void CharQueueAppend(struct CharQueue_t *queue, unsigned char value);

void CharQueueDestroy(struct CharQueue_t *queue);

unsigned char CharQueueGet(struct CharQueue_t *queue, uint16_t idx);

unsigned char CharQueueNext(struct CharQueue_t *queue);

#endif  /* CHAR_QUEUE_H */

CharQueue_t *CharQueueInit() {
    CharQueue_t *queue = malloc(sizeof(CharQueue_t));
    // Initialize size and capacity
    queue-&gt;size = 0;
    queue-&gt;capacity = QUEUE_INIT_SIZE;
    queue-&gt;data = malloc(sizeof(unsigned char) * queue-&gt;capacity);
    queue-&gt;append = CharQueueAppend;
    queue-&gt;destroy = CharQueueDestroy;
    queue-&gt;get = CharQueueGet;
    queue-&gt;next = CharQueueNext;
    return queue;
}

void CharQueueAppend(struct CharQueue_t *queue, unsigned char value) {
    // Resize the queue if it's at capacity
    if (queue-&gt;size &gt;= queue-&gt;capacity) {
        queue-&gt;capacity *= 2;
        queue-&gt;data = realloc(queue-&gt;data, sizeof(unsigned char) * queue-&gt;capacity);
    }
    queue-&gt;data[queue-&gt;size] = value;
    queue-&gt;size++;
}

void CharQueueDestroy(struct CharQueue_t *queue) {
    free(queue-&gt;data);
    free(queue);
}

unsigned char CharQueueGet(struct CharQueue_t *queue, uint16_t idx) {
    if (idx &gt;= queue-&gt;size || idx &lt; 0) {
        return 0;
    }
    return queue-&gt;data[idx];
}

unsigned char CharQueueNext(struct CharQueue_t *queue) {
    char data = queue-&gt;data[0];
    queue-&gt;size = queue-&gt;size - 1;
    // Move the pointer up the stack -- Does this leak memory?
    *queue-&gt;data++;
    return data;
}

int main(void) {
    CharQueue_t *queue = CharQueueInit();
    int i;
    for (i = 0; i &lt; 256; i++) {
        queue-&gt;append(queue, i);
    }
    printf("Queue Size is %d\r\n", queue-&gt;size);
    for (i = 0; i &lt; 256; i++) {
        printf("#%d in queue: 0x%d\r\n", i, queue-&gt;next(queue));
    }
    return 0;
}
Just a few things:


uint16_t for a size type is really small. To be precise, the maximum value of uint16_t is 2^16 - 1, or 65535. That's 64 kb of data only, which is not enough for most applications, if you consider that modern PCs have gigabytes of memory available. For sizes in general, size_t suggests itself, and offers plenty of bits for addresses (on most modern pc architectures, at least).
Why all of these functions pointers in the struct? Unless your main design goal is maximum flexibility and genericity, all you're likely achieving is bloating your struct.
Comparing a variable of type uint16_t against 0 as in if (idx &gt;= queue-&gt;size || idx &lt; 0) is pointless. The u in uint16_t stands for "unsigned", and variables of the type can only take values in the range [0, 2^16 - 1].
// Move the pointer up the stack -- Does this leak memory?
*queue-&gt;data++;


Not only does this leak data, it makes freeing the memory actually impossible! free can only be called on pointers directly obtained from  malloc and friends. As soon as you increment the pointer, free will automatically cause undefined behavior. Under all circumstances, you have to keep a pointer to the start around, or do some index shenanigans.
sizeof(unsigned char) is guaranteed to be 1 by definition. You don't need to multiply by that when you're allocating memory.
It's usually a good idea to check for malloc failures. If malloc fails to allocate memory, it returns NULL, which can be easily checked against and which allows you to exit gracefully, rather than (hopefully) crashing with a segfault.
For that very same reason, i.e. that malloc can fail, directly assigning from realloc is dangerous, since realloc can also fail and return NULL, in which case you lose the pointer to the original memory if you're not careful (leading to a certain leak down the line).
Similarly, check for NULL on all the pointers you receive from a user. Calling any of your functions with NULL as the first argument is going to lead to undefined behavior, which you should guard against.