I've been searching around for a good while now on google and a few text books and I can't seem to understand why it is, when building a linked list, that the nodes need to be pointers.
Eg. If i have a node defined as:
typedef struct Node{
int value;
struct Node *next;
} Node;
why is it that in order to create a linked list, I would say:
Node *a = malloc(sizeof(Node));
Node *b = malloc(sizeof(Node));
a->value = 1;
b->value = 2;
a->next = b;
b->next = NULL;
rather than:
Node a, b;
a.value = 1;
b.value = 2;
a.next = &b;
b.next = NULL;
To my understanding, the list will still be able to be referenced and traversed as normal, the only difference is the use of the dot, ampersand syntax rather than the arrow?
You can create the list in a way that you mentioned.
But you must care for the life time of the list members. If your
Node a, b;
are in scope of a function then these are lost after the return of that function.
When you use pointers then you usually use the heap and the instances live until they are deleted.
Your first example will not work. You are declaring two Node pointers. However, since you do not initialize these to anything, it is illegal to deference them, since they don't point to anything. You must first use something like malloc to declare memory for them to point to, or assign them to a previously declared local variable. However, you must also remember to call free when you are done with the memory.
In the second example, you are declaring two Node variables, which you use to store instances of the Node. These will be allocated on the stack if they are local variables, and they will live for as long as they are in scope. They hold valid memory, and thus you can use them in the way you have demonstrated.
Related
I created a structure for a linked list node in C as follows.
struct node
{
int data;
struct node *next;
} *START = NULL;
Then when I need to access the properties of the structure, I create a pointer to structure as follows.
struct node *node1;
node1 -> data = 12;
node1 -> next = NULL;
I want to know if we can use
struct node node1
instead of the current declaration and what changes would that make in the program.
Also, I want to know why *START=NULL is outside the structure and what its data type is ?
You need to allocate the memory for the struct. Here you have a simple function which appends the node to the end of the list
struct node
{
int data;
struct node *next;
} *START = NULL;
struct node *append(int data)
{
struct node *node1 = malloc(sizeof(*node1));
struct node *list = START;
if(node1)
{
node1 -> data = data;
node1 -> next = NULL;
if(list)
{
while(list -> next)
{
list = list -> next;
}
list -> next = node1;
}
else
{
START = node1;
}
}
return node1;
}
START is the variable of type pointer to the struct node.
struct node *node1;
node1 -> data = 12;
node1 -> next = NULL;
First, the above code doesn't initialize node1 to a valid memory address. It merely creates a pointer called node1 which can take the address of a variable of struct node type which hasn't been properly initialized.
You need to change the code to the following.
struct node *node1 = (struct node*)malloc(sizeof(struct node));
node1 -> data = 12;
node1 -> next = NULL;
The above will allocate memory for struct node type and initialize node1 with the address of the allocated memory.
Now let's get to your questions.
I want to know if we can use
struct node node1
instead of the current declaration and what changes would that make in the program.
You can use the above declaration in your program, but it will create node1 on stack rather than on heap. Probably this is not the behaviour you want since the variable made on stack has the lifetime of the scope in which it's created. In your case I assume you want to create a linked list (or similar structure), therefore you need the list to be accessible even after the function in which the appending happens returns.
Anyway, if you created the variable on stack you can simply use . operator to access structure members.
struct node node1;
node1.data = 12;
node1.next = NULL;
Also, I want to know why *START=NULL is outside the structure and what
its data type is?
It simply defines a pointer variable named START that can point to struct node and initialize it with NULL.
[...] when I need to access the properties of the structure, I create
a pointer to structure as follows.
struct node *node1;
node1 -> data = 12;
node1 -> next = NULL;
Not exactly. You have declared node1 as a variable of type struct node *, but you have not created a pointer, in the sense that you have not given that variable a value that points to any structure. Subsequently attempting to access a struct node via that variable's (indeterminate) value therefore produces undefined behavior. Among the more likely outcomes are that your program crashes or that unexpected changes are made to random objects in its memory.
To be able to use node1 to access a struct node as you show, you first need to assign it to point to one (or at least to memory that can be made to contain one). You can do that either by assigning it the address of an existing struct node or by allocating memory sufficient for a struct node and assigning its address to node1. More on those alternatives later.
I want to know if we can use
struct node node1
instead of the current declaration and what changes would that make in
the program.
You definitely can declare node1 as a struct node instead of as a pointer to one. In the scope of such a declaration, you would access its members via the direct member-access operator (.) instead of via the indirect one (->):
node1.data = 12;
node1.next = NULL;
Furthermore, one of the ways to obtain a pointer to a struct node would be to use the address-of operator (&) to obtain that structure's address:
struct node *node_ptr = &node1;
HOWEVER, the lifetime of the node1 object declared that way ends when control passes out of the innermost block in which the declaration appears (if any), any pointers to it notwithstanding. As such, that usually is not what you want for a linked-list node.
For linked-list applications (among others), one generally wants an object whose lifetime doesn't end until you say it should do, and that can be achieved by dynamically allocating memory for the structure. For example,
struct node *node_ptr = malloc(sizeof(*node_ptr));
The allocated memory remains allocated until you explicitly free() it, whether in that scope or in another. Either way, to access the structure members through a valid pointer, one uses the indirect access operator, as in your example:
node_ptr->data = 42;
node_ptr->next = NULL;
or, equivalently, one first dereferences the pointer and then uses the direct member access operator:
(*node_ptr).data = 42;
(*node_ptr).next = NULL;
.
Also, I want to know why *START=NULL is outside the structure and what
its data type is ?
You said that you wrote the code. If you don't know the significance of the *START=NULL part, then what is it doing in your code?
In any event, it is analogous to the *node_ptr = &node1 above. START is declared (at file scope) as a pointer to a struct node, and its initial (pointer) value is assigned to be NULL, which explicitly and testably does not point to any structure.
When do we use structure variable and structure pointer?
Depends on the use case. When you create a structure variable you actually allocate the structure object on the stack. When you use pointer you usally allocate the structure object in heap memory and point to this object via the pointer.
You can resize and deallocate the dynamically allocated structure object whenever you want to, while the static can't be changes in size and it is only destroyed once the scope of it ends (in case of an automatic structure variable).
More details to the difference between static vs. dynamic allocation you can find in the links below the answer.
Which way you choose, depends on what you want to do and how you want to do it.
I want to know if we can use struct node node1 instead of the current declaration and what changes would that make in the program.
That would make node1 a variable of the structure itself; it would not be a pointer to an object of the structure only anymore.
Beside the things mentioned above and others, the access to the members would be different:
node1 . data = 12;
node1 . next = NULL;
Also I want to know why *START=NULL is outside the structure and what it's data type is.
START is of type struct node * (a pointer to struct node) and initialized to NULL. It's definition is outside because it isn't a member, it is a pointer to an object of the structure.
Note that you need to assign a pointer to struct node to point to an object of struct node, but that is not what you did at:
struct node *node1;
node1 -> data = 12;
node1 -> next = NULL;
So, this would invoke undefined behavior.
Allocate memory for the structure:
struct node *node1 = calloc (1, sizeof(*node1));
if (!node1)
{
fputs("Failure at memory allocation for node1!", stderr);
return EXIT_FAILURE;
}
node1 -> data = 12;
node1 -> next = NULL;
Related regarding point 1.:
What and where are the stack and heap?
Stack variables vs. Heap variables
Why would you ever want to allocate memory on the heap rather than the stack?
Which is faster: Stack allocation or Heap allocation
Okay this question may sound stupid to the amateur programmers . But seriously this is bothering me and a solemn answer to this doubt of mine is welcomed. I have just started to take my first ever course in data structures. And what is bothering me is this:
Assuming C is used,
//Implementing a node
struct Node
{
int data;
struct *Node;
};
Now while creating a node why do we use the dynamic memory allocation technique where we use malloc(). Can't we just create a variable of type ' Struct Node '.
i.e. something like:
struct Node N1;
//First node - actually second where !st Node is assumed to be Head.
struct Node *Head = &N1;
struct Node N2;
N2.(*Node) = &N1;
Well some parts of my code may be incorrect because I am only a beginner and not well versed with C. But by know you may have understood what I basically mean. Why don't we create variables of type Node of an Array of type Node to allocate memory t new nodes why get into the complexity of dynamic memory allocation?
First off, you have an error in how you declare your struct. struct * by itself does not denote a type. You have to give the full type name:
struct Node
{
int data;
struct Node *Node;
};
You can certainly use local variables as above to make a linked list, however that limits you to a fixed number of list elements, i.e. the ones you explicitly declare. That would also mean you can't create a list in a function because those variables would go out of scope.
For example, if you did this:
struct Node *getList()
{
struct Node head, node1, node2, node3;
head.Node = &node1;
node1.Node = &node2;
node2.Node = &node3;
node3.Node = NULL;
return &head;
}
Your list would be restricted to 4 elements. What of you needed thousands of them? Also, by returning the address of local variables, they go out of scope when the function returns and thus accessing them results in undefined behavior.
By dynamically allocating each node, you're only limited by your available memory.
Here's an example using dynamic memory allocation:
struct Node *getList()
{
struct Node *head, *current;
head = NULL;
current = NULL;
// open file
while (/* file has data */) {
int data = /* read data from file */
if (head == NULL) { // list is empty, so create head node
head = malloc(sizeof(struct Node *));
current = head;
} else { // create new element at end of list
current->next = malloc(sizeof(struct Node *));
current = current->next;
}
current->data = data;
current->Node = NULL;
}
// close file
return head;
}
This is psedo-code that doesn't go into the details of reading the relevant data, but you can see how you can create a list of arbitrary size that exists for the lifetime of the program.
If these variables are local, defined inside a function's scope (i.e. stored on the stack), you shouldn't do this, because accessing them after leaving their scope will result in undefined behavior (their contents will likely be overwritten as you call other functions). In fact, any time you return a pointer to a local, stack based variable from your function, you are doing the wrong thing. Given the nature of C, this is problematic since nothing will warn you you are doing something wrong, and it will only fail later when you try to access this area again.
On the other hand, if they are declared as global variables (outside any other function), then you are simply limited by the number of variables declared that way.
You can potentially declare many variables, but keeping track of which one is "free" for use will be painful. Sure, you can even go a step further and say you will have a global preallocated array of nodes to prevent using malloc, but as you are doing all this you are only getting closer to writing your own version of malloc, instead of sticking to the existing, dynamic one.
Additionally, all preallocated space is wasted if you don't use it, and you have no way of dynamically growing your list in runtime (hence the name dynamic allocation).
Here is some good reasons to use dynamic memory
When you declare node struct Node N1;this node will store on stack memory. After scope of the node that will get destroy auto.But in case of dynamic you have handle to free the memory when you done.
When you have some memory limitation.
When you don't know the size of array then dynamic memory allocation will help you.
One issue could be that you cannot use another function to add a new node to your list.
Remember that automatic variables - like the ones created by struct Node node100; - have scope only inside the function in which they are defined. So when you do something like this:
int main()
{
struct Node *head;
/* Some code there you build list as:
head ---> node1 ---> node2 --> .. ---> node99
*/
/* Add a new node using add_node function */
add_node(head, 555);
/* Access the last node*/
}
void add_node(struct Node *head, int val)
{
/* Create new node WITHOUT using malloc */
struct Node new_node;
new_node.data = val;
/* add this node to end of the list */
/* code to add this node to the end of list */
/* last_element_of_list.next = &new_node*/
return;
}
Now you think that you have added a new node to the end of the list. But, unfortunately, its lifetime ends as soon as the add_node function returns. And when you try to access that last node in your main function your program crashes.
So, to avoid this situation you will have put all your code in one single function - so that the lifetime of those nodes do not end.
Having all your code in ONE function is bad practice and will lead to many difficulties.
This was one situation that asks for a dynamic memory allocation, because, a node allocated with malloc will be in scope untill it is freed using free, and you can put code that do different things in different functions, which is a good practice.
You don't have to use dynamic memory to create a linked list, although you definitely don't want to create separate variables for each node. If you want to store up to N items, then you'd need to declare N distinct variables, which becomes a real pain as N gets large. The whole idea behind using a linked list is that it can grow or shrink as necessary; it's a dynamic data structure, so even if you don't use malloc and free, you're going to wind up doing something very similar.
For example, you can create an array of nodes at file scope like so:
struct node {
int data;
struct node *next;
};
/**
* use the static keyword to keep the names from being visible
* to other translation units
*/
static struct node store[N]; /* our "heap" */
static struct node *avail; /* will point to first available node in store */
You the initialize the array so each element points to the next, with the last element pointing to NULL:
void initAvail( void )
{
for ( size_t i = 0; i < N - 1; i++ )
store[i].next = &store[i + 1];
store[N - 1].next = NULL;
avail = store;
}
To allocate a node for your list, we grab the node avail points to and update avail to point to the next available node (if avail is NULL, then there are no more available nodes).
struct node *getNewNode( void )
{
struct node *newNode = NULL;
if ( avail ) /* if the available list isn't empty */
{
newNode = avail; /* grab first available node */
avail = avail->next; /* set avail to point to next available node */
newNode->next = NULL; /* sever newNode from available list, */
} /* which we do *after* we update avail */
/* work it out on paper to understand why */
return newNode;
}
When you're done with a node, add it back to the head of the available list:
void freeNode( struct node *n )
{
n->next = avail;
avail = n;
}
We're not using dynamic memory in the sense that we aren't calling mallic or free; however, we've pretty much recapitulated dynamic memory functionality, with the additional limitation that our "heap" has a fixed upper size.
Note that some embedded systems don't have a heap as such, so you'd have to do something like this to implement a list on such systems.
You can write a singly linked list with out malloc , but make sure the implementation is done in main. but what about writing program for traversing , finding least number ,etc . these struct node variables will go out of scope .
struct node{
int a;
struct node* nextNode;
};
int main()
{
struct node head,node1,node2;
head.a=45;
node1.a=98;
node2.a=3;
head.nextNode=&node1;
node1.nextNode=&node2;
node2.nextNode=NULL;
if(head.nextNode== NULL)
{
printf("List is empty");
}
struct node* ptr=&head;
while(ptr!=NULL)
{
printf("%d ",ptr->a);
ptr=ptr->nextNode;
}
}
void insert(list **l, int x)
{
list *p;
p = malloc(sizeof(list));
p->item = x;
p->next = *l;
*l=p;
}
Why did we use double pointer? Could we have done the same thing using single pointer? I saw this example in the book "The Algorithm Design Manual" page 69 2nd Edition.
List is basically node, just for refernce.
Could we have done the same thing using single pointer?
You could have done that using a single pointer with a minor update.
Return the pointer that was allocated and make sure that the function call is changed appropriately.
list* insert(list *l, int x)
{
// list = *p
// What was that? That is not valid code.
list* p = malloc(sizeof(list));
p->item = x;
p->next = l;
return p;
}
and use it as
list* l = NULL;
l = insert(l, 10);
Parameters in C are passed by value. So in order to make some changes to a variable in a function, we have to tell that function the address of the variable. This enables it to change the value of the variable indirectly by writing data to the corresponding memory.
As a result, to modify an int, you have to pass an int *. In your case, to modify a list *(the type of p->next), you have to pass a list **.
using double pointer is justified here because in the function you insert node in the header of list, so the variable l will be changed with the new header *l=p;
*l->|node1|->|node2| //initial value
p->|nodeP| //after p = malloc(sizeof(list)); p->item = x;
p->|nodeP|->|node1|->|node2| //p->next = *l;
*l->|nodeP|->|node1|->|node2| //after *l=p
in this case function is called like this:
list *head;
insert(&head, 4);
For your question:
Could we have done the same thing using single pointer?
Yes, the function will look like this:
list *insert(list *l, int x)
{
list *p;
p = malloc(sizeof(list));
p->item = x;
p->next = l;
return p;
}
you can call function in this case like this:
list *head;
head = insert(head, 4);
Basically u might be calling the insert function using insert(&head,x);
Previously head would have stored the address of your first node in the
linked list and u give the address of the head node to l and to access the
address of a u need to dereference it once and to change its value u need to
dereference it twice.
and obviously u can do it without double pointers just giving the value of
head to l insert(head,x)....and in the function declaring insert(int *l,int
x)
suppose
address of a(first node)=123
value of head=123
address of head =456
l(storing address of head)=456
In order to get the address of the first node dereference once
*l=123
in order to change value or to go to the next node you dereference it twice
for visual satisfaction have a look at the diagram image i tried to figure
out for your better understanding.
----------
[Here's an diagram which will give you a clear idea abt how the double pointer
is working here][1]
[1]: http://i.stack.imgur.com/HQOaa.jpg
You need to use double pointer in this example because you would like to also change the starting node of your list. So basically when you're inserting a new element, you also want to make the node that contains it the first one of your list. If you pass only a single pointer(list *l) and you assign to it the newly created node (p), the changes (and by changes I mean the fact that it will be the first node of the list) would only be available inside your function, and would not be propagated outside it.
To make it more clear, if you're taking in a simple pointer (list *l) you're basically copying the address stored by the list* variable that's sitting outside your function, in the newly created pointer (the l parameter). So the l variable inside your function is a different pointer (a different location in memory compared to the pointer variable outside your function) containing the same address as the pointer outside the function. So that's why assigning the newly created element to this l single-pointer would only make the the newly inserted element the first only local (function scope).
Compared to the alternative method when you're taking in a double pointer (so list **l), what really happens is that by passing the outer pointer variable to the function, you're actually passing the address of the outside pointer, not to be confused with the address that the pointer is containing. (take care since you will have to call the function something like this: insert(&l, 2)). This way you will still have the address contained by the outer pointer by dereferencing it and using it as rvalue (p->next = *l) and at the same time you have the address of the outside variable, so when you're making *l = p (notice *l is used as lvalue here), you're actually dereferencing the double pointer and in consequence you will obtain the the address of the real variable (the outside one), assigning to it the newly created node. In other words you're actually setting the newly created node as the starting node but this time also outside the function.
Really hope this is not extremely confusing.
This is what I have right now for my code (just a simple BST)
typedef struct bsn{
int val;
struct bsn *left, *right;
} bsn_t;
typedef struct bst{
bsn_t *root;
int size;
} bst_t;
My question is that for the functions which I'll use, the input is an address like this
void init( bst_t * tree )
How would I use this? This is what I have right now but I'm not sure if it's correct or not
tree->size = 0;
tree->root = NULL;
Also for other functions like
bool insert( bst_t *tree, int val )
I want to declare a temp node to use. Does this work?
bsn_t temp = (bsn_t *) malloc (sizeof (bsn_t));
And my last question is that how would I check if a node is null or not. I tried
bsn_t visitor = (bsn_t*)malloc(sizeof(bsn_t));
visitor = *tree->root;
Then doing
if (visitor != NULL)
But when I compile it says that
'!=': illegal for struct
Please help..
In your tree, the connections between the nodes and the connection from outside, i.e the root, are all pointers. They point to nodes that are allocated on the heap with malloc.
If you define a local variable like:
bsn_t temp;
you get a node on the stack that will be invalid after the returning from the function. In your case, you should never need to create nodes on the stack. You should work with pointers to nodes throughout, which point to existing nodes, to frshly allocated nodes or to nothing (NULL).
So:
bsn_t *temp = malloc (sizeof (bsn_t));
(I've removed the cast to (bsn_t *). It is strange that in the original code, you have cast the return value from malloc to a pointer type when assigning to a struct.)
As for your second question, your code:
bsn_t visitor = (bsn_t*)malloc(sizeof(bsn_t));
visitor = *tree->root;
is wrong in several places. First, as above, the visitor should be a pointer to a node, not a node struct.
Then the visitor is supposed to travel from the root down the tree. By doing so, it does not create any new nodes, so there is no need to malloc at all. Remember, malloc gives you new memory on the heap, in effect creating a node. The visitor just points to existing objects. One object can have more pointers pointing to them.
Even if malloc were the right ting to do, you shouldn't malloc and then overwrite the pointer that holds the (so far only) handle to the ne memory.
You've also got the * wrong. The visitor is a pointer ans tree->root is a pointer, too, so there's no need to dereference. What you have done is to copy the contents of the root to your local struct.
What you want to do is someting like this:
bsn_t *visitor = tree->root;
while (visitor != NULL) {
// Do stuff
visitor = visitor->right; // or left, whatever
}
The use of asterisks in declaration and use may be confusing. In a declaration, you use a start to make a thing a pointer:
bsn_t node; // uninitialised node struct on the stack
bsn_t *pnode; // uninitialised pointer to node
After that, when you use the pointer variable, the unadorned name refers to the the pointer. An asterisk means that you dereference it to get at what the pointer points to. When you work with structures, you usually won't see many stars, because the -> syntax is preferred, but node->left is essentially the same as (*node).left.
You can't assign memory addresses (The return value from a malloc call) to struct typed variables. You need to assign these to pointer typed variables.
bsn_t *temp = (bsn_t *) malloc (sizeof (bsn_t));
bsn_t *visitor = (bsn_t*)malloc(sizeof(bsn_t));
Then you won't have compile time errors.
How would I use this? This is what I have right now but I'm not sure if it's correct or not
tree->size = 0;
tree->root = NULL;
Yes this is the right way to use it. If you have a pointer you can access the struct elements with(->) operator otherwise, use . (dot) operator.
bsn_t temp = (bsn_t *) malloc (sizeof (bsn_t));
Yes this is the right way as well but you are missing a '*' before temp on left hand side which states that this is a pointer type variable. Remember, it will allocate the object in heap so you have to explicitly free that memory when you no longer need it. Also, note that casting the newly allocated memory to (bsn_t *) is optional, if you don't do, compiler will implicitly do it for you depending upon your l-value type.
bsn_t visitor = (bsn_t*)malloc(sizeof(bsn_t));
Again, it should be bsn_t *visitor on your left hand side.
visitor = *tree->root;
Following that it should be *visitor = *tree->root;
Also, try to make your code more readable by writing *(temp->root) instead of *temp->root.
if (visitor != NULL) Now you should be able to use this since visitor is a pointer now.
in order to create a linked list(which will contain an attribute of next and previous node),i will be using pointers for the 2 next and previous nodes,yet i was wondering if i could complete the code without using malloc(allocating memory):
for example:
instead of malloc-ing:
link *const l = (link *)malloc(sizeof(link));
if(l == NULL)
/* Handle allocation failure. */
...
l->data = d;
l->next = list->head;
head = l;
can i simply create a new link variable with the attributes formatted(value,pointer to next and previous link),and simply link the last link in my last link in the chain to this one?
my list file is b,for example.
link i;
i.date=d;
getlast(b).next=&i
i appologize ahead for the fact i am new to c,and will be more than glad to receive an honest solution :D
edit:
i tried using malloc to solve the matter.i will be glad if anyone could sort out my error in the code,as i can not seem to find it.
#include <stdio.h>
#include <malloc.h>
struct Node{
int value;
struct Node * Next;
struct Node * Previous;
};
typedef struct Node Node;
struct List{
int Count;
int Total;
Node * First;
Node * Last;
};
typedef struct List List;
List Create();
void Add(List a,int value);
void Remove(List a,Node * b);
List Create()
{
List a;
a.Count=0;
return a;
}
void Add(List a,int value)
{
Node * b = (Node *)malloc(sizeof(Node));
if(b==NULL)
printf("Memory allocation error \n");
b->value=value;
if(a.Count==0)
{
b->Next=NULL;
b->Previous=NULL;
a.First=b;
}
else
{
b->Next=NULL;
b->Previous=a.Last;
a.Last->Next=b;
}
++a.Count;
a.Total+=value;
a.Last=b;
}
void Remove(List a,Node * b)
{
if(a.Count>1)
{
if(a.Last==b)
{
b->Previous->Next=NULL;
}
else
{
b->Previous->Next=b->Next;
b->Next->Previous=b->Previous;
}
}
free(b);
}
Yes - you can do that.
e.g.
link l1,l2;
l1.next = &l2;
l2.next = NULL;
Is a perfectly fine and valid linked list of 2 nodes.
You could also create a bunch of nodes, and link them together based on your needs, e.g. create a linked list of the argv:
int main(int argc, char *argv[])
int i;
link links[100];
for (i = 0; i < argc && i < 100; i++) {
//assuming the nodes can hold a char*
links[i].data = argv[i];
links[i].next = NULL;
if (i > 0)
links[i-1].next = &links[i];
}
There are of course some drawbacks:
The number of nodes is determined at compile time in these examples. (in the last example one could malloc a buffer for argc
nodes instead of hardcoding 100 though)
The lifetime of these nodes are the scope they are declared in, they no longer exists when the scope ends.
So you cannot do something like this:
void append_link(link *n, char *data)
{
link new_link;
n->next = &new_link;
new_link.next = NULL;
new_link.data = data;
}
That is invalid, since when append_link ends, the new_link is gone. And the passed in n->next now points to a local variable that is invalid. If new_link instead was malloc'ed, it will live beyond this function - and all is ok.
Not really.
You could create a variable for each and every node in your list, but what happens when you want another node? Fifty more nodes? These variables also won't hang around after you've left the scope they were defined in, which means you'd either have to make everything global or use static storage and expose a pointer to them. This means that all pointers to them after that scope will be invalid. These are both very ugly solutions.
If you don't understand what I mean by scope, here's a quick example:
int main() { /* Entering function scope. */
int x = 5;
{ /* Entering block scope. */
int y = 7;
printf("%d\n", y);
} /* Exiting block scope, all variables of this scope are gone. (y) */
printf("%d %d\n", x, y); /* Won't compile because y doesn't exist here. */
} /* Exiting function scope, all non-static storage variables are gone. (x)
You could also create a global array, thinking that this gets around having a lot of different variables, but if your solution is to implement this using an array, why are you using a linked list and not an array? You've lost the benefits of a linked list by this point.
There are only two ways in C to create in-memory data structures that don't have a fixed-at-compile-time size:
with allocated storage duration, i.e. via malloc.
with automatic storage duration, which in terms of implementation, means "on the stack", either using variable-length arrays or recursion (so that you get a new instance at each level of recursion).
The latter (automatic storage) has the property that its lifetime ends when execution of the block where it's declared terminates, so it's difficult to use for long-lived data. There's also typically a bound on the amount of such storage you can obtain, and no way to detect when you've exceeded that bound (typically this results in a crash or memory corruption). So from a practical standpoint, malloc is the only way to make runtime-dynamic-sized data structures.
Note that in cases where your linked list does not need to have dynamic size (i.e. it's of fixed or bounded size) you can use static storage duration for it, too.
Memory for new nodes has to come from somwhere. You can certainly create individual variables and link them manually:
link a, b, c;
...
a.next = &b;
b.next = &c;
c.next = NULL;
As you can imagine, this approach doesn't scale; if you want more than 3 elements in your list, you'd have to allocate more than 3 link variables. Note that the following won't work:
void addToList( link *b )
{
link new;
...
b->next = &new;
}
because new ceases to exist when the addToList exits, so that pointer is no longer meaningful1.
What you can do is use an array of link as your "heap", and allocate from that array. You'll need to keep track of which elements are available for use; an easy way of doing that is initializing the array so that each a[i] points to a[i+1] (except for the last element, which points to NULL), then have a pointer which points to the first available element. Something like the following:
// You really want your "heap" to have static storage duration
static link a[HEAP_SIZE];
// Initialize the "heap"
for ( size_t i = 0; i < SIZE - 1; i++ )
a[i].next = &a[i+1];
a[i].next = NULL;
// Set up the freeList pointer; points to the first available element in a
link *freeList = &a[0];
// Get an element from the "heap"
link *newNode = freeList;
freeList = freeList->next;
newNode->next = NULL;
// Add a node back to the "heap" when you're done with it:
deletedNode->next = freeList;
freeList = deletedNode;
Again, you're limited in how many list nodes you can create, but this way you can create a large enough "heap" to satisfy your requirements.
1. Obviously, the phsyical memory location that new occupied still exists, but it's now free for other processes/threads to use, so the value contained in that address will no longer be what you expect.