la free in questa maniera dealloca tutto correttamente? Inoltre leggevo da qualche parte che è consigliato settare a NULL quando si dealloca (facendo cioè un key_element = NULL dopo la chiamata alla free?), anche se non ho ben capito il motivo.
[/CODE]

Ad un'occhiata molto veloce mi sembra che deallochi tutto nel modo corretto.
Bisogna settare il puntatore a NULL per poterci richiamare free(): free(NULL) non va niente, free(ptr) va in crash se ptr punta ad un'area di memoria che è già stata deallocata.

[QUOTE
qui avrei un dubbio: non esiste un modo per creare una versione ricorsiva della find?
proprio perché nel passo ricorsivo come primo parametro vuole un tipo list_t* piuttosto che un elem_t* . Devo necessariamente arrangiarmi con la versione iterativa? :master:
Non serve, fai due funzioni:
codice:
elem_t* find_element(elem_t* ls, void* key, int (*cmp)(void*, void*));

list_t* find_ListElement(list_t* ls, void* key)
{
    return find_element(ls->head, key, compare);
}
In cui la seconda sulla lista ti fa da wrapper sulla prima.

1) perché la new_list prende come argomenti le funzioni compare, copyk e copyp? Nel momento in cui creo la lista di tipo list_t non dovrei comunque poter avere accesso alle 3 funzioni in quanto all'interno della struct di definizione?
Nella definizione della struct ci sono dei puntatori a funzione: quei puntatori però devi inizializzarli ad una vera funzione. Altrimenti, che codice potrebbero mai eseguire?
E' un po' come quando dichiari un puntatore ad un intero... poi dovrai pur inizializzarlo a qualche intero. Qui è la stessa cosa: dei puntatori a funzione vanno inizializzati per puntare ad una funzione.

2) perché la free prende in ingresso un puntatore ad un puntatore alla lista? Mi si ripresenta lo stesso problema della find. Avesse preso in ingresso un parametro elem_t*, potevo facilmente implementare questa versione ricorsiva (ammettendo pt il primo elemento della lista):
Il doppio puntatore serve per essere sicuri che venga inizializzato a NULL in modo da essere più sicuro:
codice:
void free_List(list_t** ls)
{
    procedura_che_elimina_la_lista( *ls ); //(*ls) è un list_t*
    (*ls) = NULL;    //ora il puntatore è a NULL e il programma non esplode se rifai una free
}