0

I've read these two posts/answers regarding the use of double pointers/passing by reference

When printing out a linked list, why is the original head pointer not changed

Linked list head double pointer passing

but one thing still puzzles me.

The head pointer in printList function (with head = head->next traversal) does not get changed in main, because even though we pass it by reference, the function receives a copy of the pointer/address. Which I can understand.

But how come then the whole list gets changed (updated) when inserting a node like

struct node* addLast(struct node* head, struct node* new_node) {
    if (head == NULL)
    {
        head = new_node;
        return head;
    }

    struct node* current = head;
    while (current->next != NULL)
    {
        current = current->next;
    }

    current->next = new_node;

    return head;
} 

and we call it in main

head = addLast(head, node)

I get that the principle applies to the case when head == NULL (because we return the "new" head), but if not, then we traverse the list again and insert the node.

How come then that the list gets updated (not necessarily in this particular add function only)? Is it not then that the new_node (node created by another function with malloc()) is also a "copy"?

1 Answers1

1

how come then the whole list gets changed (updated) when inserting a node?

As you rightly remark, there are two cases here:

I get that the principle applies to the case when head == NULL (because we return the "new" head)

Indeed. So your question is about the remaining cases where the list is not empty and a node is appended to it.

In that case the returned value is the same pointer as was given as argument: head does not change.

However, the node that head points to has its own next pointer, and that pointer might have changed from NULL to a pointer to the new node. So, although head does not change, the chain of next pointers will have become longer.

Just visualise what happens when we start with an empty list, and then add nodes to it with the following script:

node* createNode(int value) {
    node* newNode = malloc(sizeof(node));
    newNode->value = value;
    newNode->next = NULL;
    return newNode;
}

int main() {
    node* head = NULL;
    head = addLast(head, createNode(1));
    head = addLast(head, createNode(2));
    head = addLast(head, createNode(3));
    // ...
    return 0;
}

I just added a function to create a node instance: no surprises there (I hope!)

So when the script starts, we have head:

head: NULL

Then we call createNode(1) which returns a pointer to a node. We can represent that node with a box:

┌────────────┐
│ value: 1   │
│ next: NULL │
└────────────┘

The pointer to this node is passed as second argument to addList, so in that function we have:

 new_node         head: NULL
  ↓
┌────────────┐
│ value: 1   │
│ next: NULL │
└────────────┘

As you correctly noticed, this node reference is returned from the function addList to the caller, and the caller assigns it to its own head variable. So in the main program we now have this state:

 head
  ↓
┌────────────┐
│ value: 1   │ 
│ next: NULL │
└────────────┘

Now to the second node: it is created with createNode(2), and then addList is called with these arguments:

 head                new_node
  ↓                   ↓
┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │ 
│ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘

addList then creates another variable current which starts out with the same reference as head:

 current
 head                new_node
  ↓                   ↓
┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │ 
│ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘

The while loop condition is not true, so it will not iterate. Then current->next = new_node is executed, and this is the most important assignment: it establishes the link between the last node of the list with the new node:

 current
 head                new_node
  ↓                   ↓
┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │ 
│ next: ──────────> │ next: NULL │
└────────────┘      └────────────┘

And finally, head is returned to the caller, who assigns it to their head variable -- this is really a dummy assignment, because head didn't change. What did change is the length of the linked list that head points to.

This should explain it, but let's just add one more node: create_node(3) is passed to addList, so in addList we have this state:

 current
 head                                    new_node
  ↓                                       ↓
┌────────────┐      ┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │      │ value: 3   │ 
│ next: ──────────> │ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘      └────────────┘

This time the while condition is true, so current = current->next will bring us into this state:

 head                current             new_node
  ↓                   ↓                   ↓
┌────────────┐      ┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │      │ value: 3   │ 
│ next: ──────────> │ next: NULL │      │ next: NULL │
└────────────┘      └────────────┘      └────────────┘

The while loop will then exit, and current->next = new_node gets executed:

 head                current             new_node
  ↓                   ↓                   ↓
┌────────────┐      ┌────────────┐      ┌────────────┐
│ value: 1   │      │ value: 2   │      │ value: 3   │ 
│ next: ──────────> │ next: ──────────> │ next: NULL │
└────────────┘      └────────────┘      └────────────┘

And addList terminates by returning the (unchanged) head pointer. The main program performs again the assignment to its own head even though there was no change to that pointer.

I hope this clarifies that even though head does not change anymore, the chain of next pointers does change: the tail node's next pointer changes from NULL to the address of the new node.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you for this visual and very detailed explanation! I do understand, which is why i was confused about the printList function; because in that function, while traversing, we do assign the head->next to our head pointer (head = head->next). Does the head pointer in that case remain unchanged because we usually do not use the return value (in most cases I've come across it is a void function), or pointer to head pointer in printList functions, we rather just "go" through it? – studentkinja Dec 01 '21 at 22:14
  • It remains unchanged, because `head` is a local variable, and assigning to it does not modify the other variable with the same name in `main`, whose value was passed as argument. – trincot Dec 01 '21 at 22:19
  • Ah, I see. It’s clear now, and once more, I truly appreciate the time and detailed clarification. – studentkinja Dec 01 '21 at 22:30