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) { ... }