Linked deque implementation in C I wrote a deque implementation in C for storing char *. It uses a doubly linked list, but I'm calling it a deque since it can only push/pop from the head and tail of the list.

I don't have much experience writing C (I'm more of a Python guy) so please let me know what can be improved! I am already aware of the following:


there are no comments (I've tried to use self-explanatory names)
the testing isn't particularly rigorous


linkedlist.h

#ifndef _LINKEDLIST_H
#define _LINKEDLIST_H
#include &lt;stdlib.h&gt;

struct ll_node {
    struct ll_node *prev;
    struct ll_node *next;
    char           *data;
};

struct linkedlist {
    struct ll_node *head;
    struct ll_node *tail;
    size_t         length;
};

void ll_node_init(struct ll_node *node, char *data);
void ll_init(struct linkedlist *ll);
void ll_destroy(struct linkedlist *ll);
void ll_push_head(struct linkedlist *ll, char *data);
void ll_push_tail(struct linkedlist *ll, char *data);
char *ll_pop_head(struct linkedlist *ll);
char *ll_pop_tail(struct linkedlist *ll);

#endif


linkedlist.c

#include &lt;stdlib.h&gt;
#include "linkedlist.h"

void ll_node_init(struct ll_node *node, char *data) {
    node-&gt;prev = NULL;
    node-&gt;next = NULL;
    node-&gt;data = data;
}

void ll_init(struct linkedlist *ll) {
    ll-&gt;head = NULL;
    ll-&gt;tail = NULL;
    ll-&gt;length = 0;
}

void ll_destroy(struct linkedlist *ll) {
    struct ll_node *node = ll-&gt;head;
    struct ll_node *next;

    while(node != NULL) {
        next = node-&gt;next;
        free(node);
        node = next;
    }
}

void ll_push_head(struct linkedlist *ll, char *data) {
    struct ll_node *node = malloc(sizeof(struct ll_node));
    ll_node_init(node, data);

    if(ll-&gt;head != NULL) {
        ll-&gt;head-&gt;prev = node;
    }

    if(ll-&gt;tail == NULL) {
        ll-&gt;tail = node;
    }

    node-&gt;next = ll-&gt;head;
    ll-&gt;head = node;
    ll-&gt;length++;
}

void ll_push_tail(struct linkedlist *ll, char *data) {
    struct ll_node *node = malloc(sizeof(struct ll_node));
    ll_node_init(node, data);

    if(ll-&gt;tail != NULL) {
        ll-&gt;tail-&gt;next = node;
    }

    if(ll-&gt;head == NULL) {
        ll-&gt;head = node;
    }

    node-&gt;prev = ll-&gt;tail;
    ll-&gt;tail = node;
    ll-&gt;length++;
}

char *ll_pop_head(struct linkedlist *ll) {
    struct ll_node *node = ll-&gt;head;
    char *data;

    if(node == NULL) {
        return NULL;
    }

    if(node == ll-&gt;tail) {
        ll-&gt;tail = NULL;
    }

    ll-&gt;head = node-&gt;next;
    ll-&gt;length--;

    data = node-&gt;data;
    free(node);

    return data;
}

char *ll_pop_tail(struct linkedlist *ll) {
    struct ll_node *node = ll-&gt;tail;
    char *data;

    if(node == NULL) {
        return NULL;
    }

    if(node == ll-&gt;head) {
        ll-&gt;head = NULL;
    }

    ll-&gt;tail = node-&gt;prev;
    ll-&gt;length--;

    data = node-&gt;data;
    free(node);

    return data;
}


ll_test.c

#include &lt;assert.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include "linkedlist.h"

int main(int argc, char *argv[]) {
    struct linkedlist ll;
    char *data;
    ll_init(&amp;ll);

    ll_push_head(&amp;ll, "Alice");
    ll_push_tail(&amp;ll, "Bob");
    ll_push_tail(&amp;ll, "Charlotte");
    ll_push_head(&amp;ll, "Daniel");
    assert(ll.length == 4);

    assert(strcmp(ll_pop_head(&amp;ll), "Daniel") == 0);
    assert(strcmp(ll_pop_tail(&amp;ll), "Charlotte") == 0);
    assert(strcmp(ll_pop_tail(&amp;ll), "Bob") == 0);
    assert(strcmp(ll_pop_tail(&amp;ll), "Alice") == 0);
    assert(ll.length == 0);

    ll_destroy(&amp;ll);
    return 0;
}
Looks good. Few notes:


There is no reason to #include &lt;stdlib.h&gt; in both linkedlist.h and linkedlist.c. Pick one (I recommend linkedlist.c).
Allocation of a struct llnode instance definitely belongs to ll_node_init, and its signature should be 

static struct ll_node * ll_node_init(char * data);


Notice static: there is no reason to expose struct ll_node to the client.
Maintain your invariants. It is clear that if (ll_head != NULL) then ll_tail != NULL, and vice versa. For example, push_head should have

    if (ll-&gt;head != NULL) {
        assert(ll-&gt;tail != NULL);
        ll-&gt;head-&gt;prev = node;
    } else {
        assert(ll-&gt;tail == NULL);
        ll-&gt;tail = node;
    }

The code doesn't really care what kind of data it queues. You may consider making the structure more generic with void * data.