Hace una cosa extraña.
Está pensado para modificar la misma lista que se le pasa, pero en vez de modificar directamente el parámetro list lo que hace es modificarlo y regresar un puntero con el dato modificado. Seguramente el autor pensó en esa solución para dar un valor en caso de que list fuera NULL. Pero hubiera hecho lo mismo con un puntero a puntero y el resultado habría sido más natural.
Supongamos el siguiente trozo de código:
SList mi_lista = NULL;
mi_lista = slist_append(mi_lista, 5);
Ahora mi_lista pasa a tener un elemento con un valor en data de 5. Continuamos con la variable mi_lista tal y como la hemos dejado en el siguiente código:
mi_lista = slist_append(mi_lista, 7);
Ahora mi_lista tiene dos elementos, uno que contiene el 5 y otro que contiene el 7. Se puede representar así [5 , 7].
Pero qué pasaría si hubiera:
SList lista_uno = NULL;
SList lista_dos = NULL;
lista_uno = slist_append(lista_uno, 5);
lista_dos = slist_append(lista_uno, 7);
¿Era esto lo que esperaba el autor que se hiciera?
Casi mejor hubiera sido definir la función con un puntero a puntero.
void slist_append(SList * list, int data) {
/* Implementación de la función */
}
/* Código y más código */
SList mi_lista = NULL;
slist_append(&mi_lista, 5);
slist_append(&mi_lista, 7);
Ahora mi_lista estaría actualizada con [5 , 7] pero no habría posibilidad de darle a otra lista la posibilidad de acceder a los datos de ésta con lo que se previenen errores a la hora de escribir el programa.
Por otra parte esconder un puntero dentro de un typedef no es muy buena práctica, pero cada uno hace lo que quiere.
Por otra parte, sobre la primera pregunta: tal vez un array de punteros. Los elementos apuntados no se mueven de su sitio, lo que cambias son los punteros del array y tienes un array para acceder a ellos.