Consider insert(l,e): l is a pointer to a
header.
If l is empty l->first is
NULL. After insertion l->first is a
pointer to the first node and that node contains e.
If a list was implemented by a pointer to its first node, and an
empty list by a NULL pointer, insert would not be able to
operate on an empty list by means of side-effects.
void insert (list l,element e) { ... }
Suppose it is called on an empty list:
list l = (list) NULL; insert(l,e);
insert(NULL,e) allocates a new node to hold
e, but what to do with the pointer to that node? To
modify the original list l, we could pass
&l instead:
void insert (list *l,element e) { ... }
Better: let insert return the modified list. Invoke
with l = insert(l,e):
list insert (list l,element e) { ... }