A lot of people do it the way you described, as far as keeping track of two nodes; the first node is usually called "trunk".
It looks like this
struct node *trunk = my_newnode_func();
struct node *current = trunk;
/* do whatever with current */
current = trunk;
The order you build the list in isn't important; e.g. both of these are fine:
struct node *trunk = my_newnode_func();
struct node *current = trunk;
current->next = my_newnode_func();
current = current->next;
(etc)
or
struct node *trunk = my_newnode_func();
struct node *current = my_newnode_func();
current->next = trunk->next;
trunk->next = current;
(etc)
Another way to do this is with a doubly linked list. Those look like this:
struct node
{
void *data;
struct node *next;
struct node *prev;
}
You initialize both next and prev to NULL. That way, the first prev and last next are already nullified.
The third way to do it is to use a recursive function, passing node->next to the function, and setting the function's escape condition to something like
if (node == NULL)
return;
So the recursive function would look like
void my_recursive_function(struct node *node)
{
if(node == NULL)
return;
my_recursive_function(node->next);
}
Finally, you can make a circular list, and either use an endless loop, or set your escape condition to test the data instead of the node pointer itself (also testing the pointer itself to avoid dereferencing a null), e.g.
if(node == NULL || node->data == 2)
return;
That way, you can link the final node back to the head node. You'll still need to keep trunk and current seperate when you create the list, but the difference between this and the first method is that you dont' have to keep track of trunk seperately once you leave the function scope; any old port in a storm will do at that point.