Linked list C implementation I'm reading a data structure and algorithm book in C.  I have implemented a generic single linked list in the C programming language, and I want to know your opinion on my implementation:

linked_list.h

#ifndef LINKED_LIST_H_INCLUDED
#define LINKED_LIST_H_INCLUDED

#define list_size(list) (list-&gt;size)    

#define list_head(list) (list-&gt;head)    
#define list_tail(list) (list-&gt;tail)
#define list_is_head(list, element) (element == list_head(list))
#define list_is_tail(list, element) (element == list_tail(list))

#define list_append(list, data)     list_ins_next(list, list-&gt;tail, data)
#define list_preprend(list, data)   list_ins_next(list, NULL, data)

#define list_data(element)  (element-&gt;data)
#define list_next(element)  (element-&gt;nextElmt)

typedef struct ListElmt_{
    void *data;
    struct ListElmt_ *nextElmt;
} ListElmt;

typedef struct List_{
    ListElmt *head;
    ListElmt *tail;
    size_t size;    
    void (*destroy)(void *data);
    int (*cmp)(const void *fdata,const void *sdata);
}List;


List *list_init(void (*destroy)(void *data), 
        int (*cmp)(const void *fdata, const void *sdata));
void list_destroy(List *list);
int list_ins_off(List *list, size_t off, void *data); 
int list_ins_next(List *list, ListElmt *element, void *data); 
int list_drem_next(List *list, ListElmt *element);
int list_rem_next(List *list, ListElmt *element, void ** data);
ListElmt * list_find(List *list, void *data, ListElmt *sElmt);

#endif


linked_list.c

#include &lt;stdlib.h&gt;

#include "linked_list.h" 


List * list_init(void (*destroy)(void *data),
        int (*cmp)(const void *fdata, const void *data)) 
{   
    List *rlist;

    if((rlist = malloc(sizeof *rlist)) != NULL) { 
        rlist-&gt;head = NULL; 
        rlist-&gt;tail = NULL; 
        rlist-&gt;size = 0; 
        rlist-&gt;destroy = destroy; 
        rlist-&gt;cmp = cmp;
    }

    return rlist;
} 

void list_destroy(List *list) {

    ListElmt * el;
    void *data;

    while( (el=list_head(list)) != NULL) {
        list_rem_next(list, NULL, &amp;data);
        list-&gt;destroy(data);
    }

    free(list);
}

int list_ins_next(List *list, ListElmt *elmt, void *data) {

    ListElmt *addElmt = malloc(sizeof *addElmt);
    if(addElmt == NULL)
        return -1;

    addElmt-&gt;data = data;

    if(elmt == NULL) {
        addElmt-&gt;nextElmt = list-&gt;head;
        elmt = list-&gt;head = addElmt; // using elmt for checking list tail
    }
    else {
        addElmt-&gt;nextElmt = elmt-&gt;nextElmt;
        elmt-&gt;nextElmt = addElmt;
    }

    // updating list-&gt;tail if needed
    if(elmt == list-&gt;tail)
        list-&gt;tail = addElmt;

    list-&gt;size++;

    return 0;
}

int list_ins_off(List *list, size_t off, void *data) {
    if(off &lt; 0 || off &gt;= list-&gt;size)
        return list_append(list, data);

    size_t i;
    ListElmt *elmt = NULL;

    for(i = 0; i &lt; off; ++i)
        elmt = elmt-&gt;nextElmt;

    list_ins_next(list, elmt, data);
    return 0;
}

int list_drem_next(List *list, ListElmt *element) {
    void *data;
    int ret = list_rem_next(list, element, &amp;data);

    if(ret != 0)
        return ret;

    list-&gt;destroy(data);
    return 0;
}

int list_rem_next(List *list, ListElmt *element, void **data) {
    ListElmt *remElmt;

    // the list still not contain any element
    if(list-&gt;size == 0 || element-&gt;nextElmt == NULL) 
        return -1;

    *data = element-&gt;nextElmt-&gt;data;
    remElmt = element-&gt;nextElmt;

    element-&gt;nextElmt = element-&gt;nextElmt-&gt;nextElmt;

    if(remElmt == list-&gt;tail)
        list-&gt;tail = element;

    free(remElmt);
    list-&gt;size--;

    return 0;
}

ListElmt * find(List *list, void *data, ListElmt *sElmt) {
    if(list-&gt;size == 0 || list-&gt;cmp == NULL)    
        return NULL;

    sElmt = sElmt ? sElmt : list_head(list);    

    while(sElmt &amp;&amp; list-&gt;cmp(sElmt-&gt;data, data))
        sElmt = sElmt-&gt;nextElmt;

    return sElmt;
}
In order of appearance:


list_init

While destroy is definitely a property of the list, cmp is not; it is a property of a particular searc. Only find really cares of how to compare elements. It is better to pass a comparator to find directly.
list_desroy

may and should take advantage of knowing the list size. If perchance the list is corrupted, it is possible that the (el=list_head(list)) != NULL condition is never satisfied, and the loop becomes infinite. Counting iterations gives you an ability to fail gracefully.
Insertions

Naming is questionable. list_ins_next should be list_insert_after; list_ins_off should be list_insert_at. In any case, do not abbreviate insert.
Removals

The purpose of list_drem_next is quite unclear. Why to destroy data of the next element? It is much more straightforward to destroy data of this element. In any case, you must nullify the data pointer once data are destroyed.

The name itself is also questionable. list_destroy_data sounds better.
Missing functionality

There is no way to remove the head element.