2

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.

4
  • 3
    You rarely know how many customers are going to be handled, so you can't write the code using fixed variable names as in the second example. It works fine, but doesn't extend. How would you handle 100 elements in the list? Commented Jun 27, 2022 at 2:43
  • 1
    Re "I'm new to C language.", Dynamically allocating the nodes should be natural to you, then. This is what you'd do in other languages too. Commented Jun 27, 2022 at 3:34
  • 1
    You have pointers either way. Contrary to the question title, you seem not to be asking "why pointers?" but rather "why dynamic allocation?" Commented Jun 27, 2022 at 14:29
  • 1
    And the fact is that dynamic allocation is not inherently associated with linked lists. You can use linked lists without dynamic allocation. But dynamic allocation addresses a couple of issues that frequently arise in linked-list scenarios. Commented Jun 27, 2022 at 14:33

4 Answers 4

4

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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.

Comments

0

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.

Comments

0

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".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.