I have a struct as follows:
typedef struct Node {
void* data;
unsigned long id;
NodePtr next;
NodePtr prev;
} Node;
It is meant to be a node in a linked list ADT. I have 2 different constructors depending on what the Node needs to hold in data. One constructor uses:
NodePtr TempNode;
TempNode = malloc( sizeof(Node) );
/* Other stuff */
TempNode->data = newList();
return (TempNode);
And this seems to work just fine for letting me access that list by returning (List->current->data) where current is a Node pointer in the List Struct
However, I want to make a version of the constructor where (data) points to an int. I've read that I can do this by doing the following
void* ptr;
int x = 0;
*((int*)ptr) = x;
But with the way my constructor is set up, that would mean I have to do something like this?
*((int*)TempNode->data) = 1; // data starts at 1
And this doesn't work. I'm very new to C so I don't understand much of the terminology. I read that dereferencing (using the -> notation?) cannot be done with void*'s, but it seems to work fine in my list version of the constructor. How can I rewrite my other constructor to cast this void* to an int?
I strongly counsel against doing this, but if you really want to use the void * member to hold an integer, you can do:
Node *constructor_int(int n)
{
Node *tmp = malloc(sizeof(*tmp));
/* Other stuff */
tmp->data = (void *)n;
return(tmp);
}
This involves the minimum number of casts and avoids most problems with relative sizes of types.
The obvious, logical way to do it is to allocate an integer for the data member to point at:
Node *constructor_int(int n)
{
Node *tmp = malloc(sizeof(*tmp));
/* Other stuff */
tmp->data = malloc(sizeof(int));
*(int *)temp->data = n;
return(tmp);
}
You just have to remember to free the two memory allocations.
The code should also check that the memory allocations succeeded before using the results.
Let's talk about this
When you do something like
NodePtr TempNode;
TempNode = malloc( sizeof(Node) );
you have asked the library to reseve you some dynamic storage which is big enough for a Node. The initial values of that memory are undefined, so right now the pointer TempNode->data could point anywhere and probably does not point at memory reserved for your use.
When you do
TempNode->data = newList();
you give the pointer a (presumably, as long as newList() does something legal and sensible) valid value;
If instead you do
*((int*)TempNode->data) = 1;
you instruct the compiler to
treat TempNode->Data as a pointer-to-int,
de-reference it and
set the value of what every memory is at the other end to 1 (notice that at no point have you set data itself, just whatever it points at)
But you don't know what it points to! De-referencing it is undefined behavior and strictly a bad thing.
You are always responsible for ensuring that you do not de-reference a pointer unless it points to memory that you are entitled to use.
"How can I make data point to an area I can use?"
I'm not sure if you mean what I'm going to explain below (and it won't be short :P), but if you are asking how you can distinguish the type of the data stored in Node->data, then with that implementation you cannot.
You leave it up to the end-programmer to remember what type of data he has stored into the list (which is not a bad thing btw.. on the contrary, it is the norm). In other words, you trust the end-programmer that he/she will apply the proper casts when say printing the Node->data.
If for some reason you wish to provide a more managed API for your list, you could add one more field in a List struct, for identifying the type of the data stored in your list.
For example...
enum DataType {
DT_INVALID = 0,
DT_PTR
DT_CHAR,
DT_INT,
DT_FLOAT,
DT_DOUBLE,
...
/* not a data-type, just their total count */
DT_MAX
};
#define DT_IS_VALID(dt) ( (dt) > DT_INVALID && (dt) < DT_MAX )
typedef struct List List;
struct List {
enum DataType dt;
Node *head;
};
Of course you are free to support less or more data-types than the ones I listed in the enum, above (even custom ones, say for strings, or whatever you see fit according to your project).
So first you'll need a constructor (or initializer) for a list, something like this...
List *new_list( enum DataType dt )
{
List *ret = NULL;
if ( !DT_IS_VALID(dt) )
return NULL;
ret = malloc( sizeof(List) );
if ( NULL == ret )
return NULL;
ret->dt = dt;
ret->head = NULL;
return ret;
}
and instantiate it, say like this...
int main( void )
{
List *listInt = new_list( DT_INT );
if ( NULL == list ) {
/* handle failure here */
}
Now that you have already stored the intended data-type into the list meta-data, you are free to choose how you will implement the Node constructors. For example, a generic one could look something like this...
int list_add_node( List *list, const void *data )
{
Node *node = NULL;
size_t datasz = 0;
/* sanity checks */
if ( !list || !data || !DT_IS_VALID(list->dt) )
return 0; /* false */
node = malloc( sizeof(Node) );
if ( NULL == node )
return 0; /* false */
/* when data points to mem already reserved say for an array (TRICKY) */
if ( DT_PTR == list->dt ) {
node->data = data;
}
/* when data points to mem reserved for a primitive data-type */
else {
datasz = dt_size( list->dt ); /* implement dt_size() according to your supported data-types */
node->data = malloc( datasz );
if ( NULL == node->data ) {
free( node );
return 0; /* false */
}
memcpy(node->data, data, datasz );
}
/* add here the code dealing with adding node into list->head */
...
return 1; /* true */
}
For DT_PTR (which I flagged as TRICKY in the example) it is more safe to implement a different Node constructor, perhaps accepting 2 extra arguments, lets say elemsz and nelems, so the function can allocate elemsz * nelems bytes for copying the data contents into them, in case data points to an array, a struct or any other non-primitive type. Or you can provide an extra DT_ARR enum value specifically for arrays. You are free to do whatever suits you best.
In any case, for DT_PTR the example above relies on the caller of list_add_node to have properly allocated the passed data, and in general context this is not a good thing at all.
The code is more complicated, but you know the data-type stored in your list. So for at least the primitive data-types you can add say a printing routine that automatically casts its output according to list->dt (for non-primitive data types you should provide support for custom printing routines, usually via callback functions).
You can even take it to the extreme, and move the dt field from List to Node. In that case you implement a list of heterogeneous data in the nodes, but it gets much more complicated and also its rarely useful (if ever).
All this ADT stuff via (void *) pointers have serious performance issues, that's why speed critical implementations utilize (or abuse if you prefer) the pre-processor instead, for this kind of stuff.
Related
I'm new to C language.
studying linked list, I found it very hard to understand using pointer.
(I understand the benefit of linked list compared to array.)
Let's assume I have 3 customers and specific value to each.
struct linknode{
int data;
struct linknode *next
};
why we use pointer like (case1)
linknode *a = malloc(sizeof(Node));
linknode *b = malloc(sizeof(Node));
a->value = 1;
b->value = 2;
a->next = b;
b->next = NULL;
How about just (case2)
linknode a, b;
a.value = 1;
b.value = 2;
a.next = &b;
b.next = NULL;
Isn't it possible to make linked list with case 2?
also insert, delete being possible?
thanks.
Isn't it possible to make linked list with case 2? also insert, delete being possible?
It is possible, it just isn’t very useful. The maximum size of your list is limited to the number of variables you declare, and I doubt you’re going to want to declare more than a dozen separate variables.
Something you can do is use an array as your backing store - instead of declaring separate variables a and b you can declare an array of 10, 100, or 1000 elements, then do something like:
a[i].next = &a[j];
But you’re still limited - your list can never be bigger than the array. The advantage of using dynamic memory is that the list size isn’t limited (at least, not some fixed compile-time limit); however, it means messing with pointers.
Pointers are a fundamental part of programming in C - you cannot write useful C code without using pointers in some fashion.
Edit: A more realistic implementation of a linked list would use an insert function like
/**
* Inserts items into the list in ascending order.
*
* If the list is empty (head is NULL) or if the value
* of the new node is less than the value of the current
* head, then the new node becomes the new head of the
* list.
*
* Returns the pointer to the new node. If the allocation
* was unsuccessful, it returns NULL.
*/
struct linknode *insert( struct linknode **head, int val )
{
struct linknode *newnode = calloc( 1, sizeof *newnode );
if ( !newnode )
return NULL;
newnode->data = val;
if ( !*head )
{
/**
* list is empty, newnode becomes the head of the list.
*/
*head = newnode;
}
else if ( newnode->data < (*head)->data )
{
/**
* Value stored in newnode is less than the
* value stored at the list head, newnode
* becomes the new list head.
*/
newnode->next = *head;
*head = newnode;
}
else
{
/**
* Iterate through the list and insert the
* newnode in the correct location.
*/
struct linknode *cur = *head;
while ( cur->next && cur->next->data < newnode->data )
cur = cur->next;
newnode->next = cur->next;
cur->next = newnode;
}
return newnode;
}
and it would be used something like this:
int main( void )
{
struct linknode *list = NULL;
int val;
while ( scanf( "%d", &val ) == 1 )
{
if ( !insert( &list, val ) )
{
fprintf( stderr, "Could not add %d to list, not taking any more input...\n", val );
break;
}
}
...
}
So the elements of the list are allocated and added dynamically, and you're only limited by the amount of memory you have available.
Statically-allocated nodes (the latter) is fine, but not very useful in practice.
You'll presumably need to add nodes to your list. And chances are overwhelmingly in favour of some of them needing to be dynamically allocated. For example, nodes created in a loop would need to be dynamically allocated.
If you had a mix of statically- and dynamically-allocated nodes, you won't know which ones to free without some extra flag in each node. This would add complexity of the program. It's easier to only deal with dynamically-allocated nodes than a mix.
Your examples are not about pointers but about memory allocation and lifetime.
linknode *a = malloc(sizeof(linknode)); //creates a linknode on heap
linknode b; //creates a linknode on stack
First example creates a node on heap. It exists in memory until you free it. Second example creates a node on stack. It exists until program leaves current scope (eg. a function).
Pointers are very easy to understand, they just point somewhere. You've probably already used pointers in Java, C# or other languages (they're called different names in those languages but they work mostly the same). What's difficult to understand is object lifetime. In other languages garbage collector makes sure objects are alive as long as need them, by magic. In C it's your duty to carefully design lifetime of your objects. If you mess up lifetime, you end up using memory that was already assigned to something else or you end up with memory leaks.
In your second example, the list ceases to exist when the function returns. I guess that's not what you intended.
In both of your examples struct linknode *next is a pointer. In one you make it point to an object on heap and in other you make it point to an object on stack, but the pointer works same. It's the target of the pointer where the magic happens.
There is a way to build a similar data structure without pointers. This is the same technique used to serialize a linked data structure for storage or transmission where the pointers lose their meaning.
You allocate a large fixed static array, put your data into it, and use integers to index into the array as your "pointers". Using an integer as an index is commonly called a cursor.
typedef unsigned index;
typedef struct linknode {
int data;
index next;
} link;
link memory[ 1000 ];
index next = 0;
Then you can add data into it.
link *a = memory + next++;
link *b = memory + next++;
a->data = 1;
b->data = 2;
a->next = b - memory;
Some details would need to be worked out for a robust system like whether index 0 is considered valid or a NULL pointer, or if all the .next indices need to be pre-initialized with some non-zero "null" index. IMO, simplest is to treat 0 as NULL and initialize next to 1.
You could also create nodes as above without using pointers, but keeping track of the index is a little clumsier and the expression involving the index is more cumbersome.
index a_index = next++;
index b_index = next++;
memory[ a_index ].data = 1;
memory[ b_index ].data = 2;
memory[ a_index ].next = b_index;
Aside: There's room for improvement in your malloc calls.
linknode *a = malloc(sizeof(Node));
A better style is to use either the typename or variable name from the same line of code, so it can be verified at a glance.
linknode *a = malloc( sizeof( linknode ) );
Or, preferred by many is to use the variable itself, then you can change the type easily if you want because it's only written once.
linknode *a = malloc( sizeof *a );
By giving sizeof an expression argument (which you can do because it's an operator, not a function) you can drop the parentheses, too. The expression argument to sizeof is not evaluated, just inspected for its type. There is one weird exception if the type is variably modified, but that's too complicated to explain (I don't fully understand it). So just remember, there is a weird exception, but for the most part *a in the above code is safe because sizeof just needs the size of the type.
Think of it as "what the size would be if the malloc call succeeds".
I'm a C beginner experimenting with linked lists and I found a Github repo for one. the create function is written like this:
Node* ListCreate(void* data) {
Node *head = malloc(sizeof(Node));
if ( head != NULL) {
head->next = NULL;
head->data = data;
}
return head;
}
I was able to use it by changing the void* to int and then in main doing:
Node *root = ListCreate(5);
But then I read a little about void pointers and it seems like they can be used as generic types sort of like C++ templates, which would be useful if I could figure out how they work. I tried several things, and the closest I've got to getting it to work is no errors, but one warning:
incompatible integer to pointer conversion passing 'int' to parameter of type 'void *'
Am I missing a step here? I first felt like I should add something to the function definition, but I'm assuming the person who wrote it knows what they're doing and I just didn't use it properly in main. So is there a different way I'm supposed to pass an argument to this function?
As is mentioned by others in comment, it is more suitable to create different list for different types.
But if you want to use the same function definition, then use can pass the pointer to your data (int or char) and cast them as void *.
/* main: start */
int main(void)
{
Node *list_head; /* Points to first element in list */
Node *node_tmp; /* Just a temporary pointer */
int *pint;
char *pchar;
/* create an empty list */
list_head = NULL;
/* Note that there is no error checking done for
* malloc, which is not good
*/
/* Create a node which points to int */
pint = malloc(sizeof(int));
*pint = 10;
node_tmp = ListCreate((void *) pint);
/* Add this node to list */
list_head = add(node_tmp, list_head);
/* Create a node which points to char */
pchar = malloc(sizeof(char));
*pchar = 'c';
node_tmp = ListCreate((void *) pchar);
/* Add this node to list */
list_head = add(node_tmp, list_head);
/* print total number of nodes in list */
print_tot_nodes(list_head);
return 0;
}
Code for add and print_tot_nodes ommited are for brevity.
Note, that functions like print_tot_nodes or add will have not much issues if data points to different data types. But if you need to implement function like Node *smallest(Node *head), which returns pointer to the node having smallest element, then it may get complicated.
So, it is easier to use different list for different types. However you can cast the actual pointer to some datatype to void * if you need to use the same function definition as seen in your original post.
I think that there is a need to wrap for numeric literals(or cast?).
Like this:
void *BOX_VAR;//Not require if use GCC extension
#define BOX(type, value) ((*(type *)(BOX_VAR=malloc(sizeof(type))) = value), BOX_VAR)
#define UNBOX(type, value) (*(type *)(value))
Node *list = ListCreate(BOX(int, 5));
int v = UNBOX(int, list->data);
printf("%d\n", v);
Is it possible to build a linked list without the help of self referential structure? I.e. like this just using a pointer:
struct list{
int data;
int *nxt;
};
instead of
struct list{
int data;
struct list *nxt;
};
Yes, it's possible.
What you're proposing is type punning, and you'll probably get away with it with most compilers on most platforms, but there's not a single good reason to do it in the first place and many good reasons not to.
You might have a look at how linked lists are implemented in the Linux kernel.
Compared to the classical linked lists:
struct my_list{
void *myitem;
struct my_list *next;
struct my_list *prev;
};
using a linked list in the linux kernel looks like:
struct my_cool_list{
struct list_head list; /* kernel's list structure */
int my_cool_data;
};
Source
Sure. You could use void * instead of struct list * as your nxt member. For example:
struct list {
int data;
void *nxt;
};
Alternatively, if you're prepared to build in a guarantee that the nodes of the list will be stored into an array (which is great for cache locality and insertion speed), you could use a size_t.
struct list {
int data;
size_t nxt;
};
If your implementation provides uintptr_t (from <stdint.h>), you could merge these two types into one:
struct list {
int data;
uintptr_t nxt;
};
Another approach is to use parallel arrays as your "heap"1, with one array containing your data (whether a scalar or a struct) and another to store the index of the next "node" in the list, like:
int data[N];
int next[N];
You'll need two "pointers" - one to point to the first element in the list, and one to point to the first available "node" in your "heap".
int head;
int free;
You'll initialize all the elements in the "next" array to "point" to the next element, except for the last element which points to "null":
for ( int i = 1; i < N-1; i++ )
next[i-1] = i;
next[N-1] = -1;
free = 0; // first available "node" in the heap
head = -1; // list is empty, head is "null"
Inserting a new element in the list looks something like this (assuming an ordered list):
if ( free != -1 )
{
int idx = free; // get next available "node" index;
free = next[free]; // update the free pointer
data[idx] = newValue;
next[idx] = -1;
if ( head == -1 )
{
head = idx;
}
else if ( data[idx] < data[head] )
{
next[idx] = head;
head = idx;
}
else
{
int cur = head;
while ( next[cur] != -1 && data[idx] >= data[next[cur]] )
cur = next[cur];
next[idx] = next[cur];
next[cur] = idx;
}
}
Deleting an element at idx is pretty straightforward:
int cur = head;
while ( next[cur] != idx && next[cur] != -1 )
cur = next[cur];
if ( next[cur] == idx )
{
next[cur] = next[idx]; // point to the element following idx
next[idx] = free; // add idx to the head of the free list
free = idx;
}
Advantages of this approach:
No dynamic memory management;
No self-referential structures;
Data items are stored contiguously in memory, which may lead to better caching performance.
Disadvantages:
In this simple approach, list sizes are fixed;
Any usefully large arrays will have to be declared static, meaning your binary may have a large footprint;
The convention of using -1 as "null" means you have to use a signed type for your indices, which reduces the number of available elements for a given array type.
You could certainly use 0 as "null" and count all your indices from 1; this allows you to use unsigned types like size_t for your indices, meaning you can make your arrays pretty much as big as the system will allow. It's just that 0 is a valid array index whereas -1 (usually) isn't, which is why I chose it for this example.
1. Welcome to my Data Structures class, ca. 1987, which was taught using Fortran 77. Parallel arrays were pretty much the answer for everything (lists, trees, queues, etc.)
Hi I am making a Queue abstact data type and I ran into a problem which I will try to explain as clearly as possible.
Basically I have a two structs one for an element and one for a queue (so you can initialize multiple queues).
struct element
{
TYPE value;
struct element* next;
};
struct queue
{
struct element* head;
struct element* tail;
int element_counter;
};
And I have a function which initializes my queue struct.
int make_new_queue(struct queue* name)
{
name = malloc(sizeof(struct queue));
name->head = NULL;
name->tail = NULL;
name->element_counter = 0;
}
The problem I ran into is foolproofing this code. For example I initialised my first queue in my main function.
struct queue* first = make_new_queue(first);
But if somebody tries to do the same thing again somewhere in the middle of the code write:
first = make_new_queue(first);
it overrides it and makes the head and tail pointers NULL. The thing I can't figure out is how to make my make_new_queue function better and check if there is something in the queue that I provide it, but still let me initialise empty queues.
Sorry for my english. I hope you get the idea of what I want to do. Thanks.
Initialize it to NULL and pass a pointer to pointer:
void make_new_queue(struct queue **name)
{
if (*name != NULL) return; /* Yet initialized ? */
*name = malloc(sizeof(struct queue));
if (*name == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
*name->head = NULL;
*name->tail = NULL;
*name->element_counter = 0;
}
struct queue *first = NULL;
make_new_queue(&first);
You can simply check if you variable name is NULL.
If your variable is non-null, you can alloc and initialize your queue else just do what you want to do.
There is really no good way to enforce what you want without discipline from the user. In my opinion, the best approach is to stick to known patterns.
You can use Alter Mann's approach to pass a pointer to a pointer to update the struct. The drawback is that is that you have to separate variable declaration and initialisation. It also relies on the user to initialise the pointer to the struct to NULL. If you forget it, the queue pointer will not be initialised and you don't even know, because you can't check against NULL.
struct stuff *stuff = NULL;
stuff_initialise(&stuff);
You can use your original approach to pass in the struct and return the same struct when it was initialised or a new one. It has the same problems as the first approach plus the possibility that you forget to assign the return value. You can, however, combine allocation and initialisation:
struct stuff *stuff = stuff_new(NULL);
The simplest way is not to pass the old pointer and rely on the user not to change the pointer after allocation. After all, the functions of the standard library suffer from the same problem:
char *buf = malloc(BUFSIZE);
buf = malloc(2 * BUFSIZE); // Memory leak
buf++; // Allowed, but a bad idea
free(buf); // Ouch!
FILE *f = fopen("palomino.txt", "r");
f = stdin; // Switch horses midstream?
You should design your API so that you have matching pairs of functions that define the scope of your struct:
struct stuff *stuff = stuff_create(...);
// use stuff ...
stuff_destroy(stuff);
Good naming should make clear that stuff acts as a handle and should not be changed and that after closing or destroying the handle, it is invalid. You should see these calls as first and last statements in functions. (This method is similar to the malloc/free or fopen/fclose pairngs of the standard library.)
Finally, if you as the user of a handle want to avoid accidential changes, you can define the pointer (not what it points to) as const:
struct stuff *const handle = stuff_create("X");
handle = stuff_create("Y"); // Compiler error
But I rarely see this. C programmers usually just use an unadorned pointer and remember to keep their fingers off the hotplate.
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.