I don't understand why I don't get a bug when I initialize a pointer with *p1, where p1 is a double pointer

92 Views Asked by At

The program doesn't have a specific function; I am just currently studying lists and I have a hard time understanding why I don't get a compile error in line where Node *curr = *root; Shouldn't I initialize Node *curr with an address using &? But when I set a double pointer and a pointer and I initialize the second one with *p, where p is the double pointer, I get a compile error and the program doesn't even run. Both scenarios seem the same to me, and I can't see the difference.

#include <stdio.h>
#include <stdlib.h>
   
typedef struct Node {
    int x;
    struct Node *next;
} Node;

void insert_end(Node **root, int value) {

    Node *new_node = malloc(sizeof(Node));
    new_node->next = NULL;
    new_node->x = value;

    //We have to use the loop to reach the
    //  end of the list where we add our additional box
    Node *curr = *root;
    while (curr->next != NULL) {
        curr = curr->next;
    }
    curr->next = new_node;
}

int main() {
     Node *root = malloc(sizeof(Node));
     root->x = 15; //1o element
     root->next = NULL;

    //the last element of a list is always initialized with null
    //exit loop when *next = NULl

    //it matters the order of freeing the memory
    insert_end(&root, -2);
    for (Node *curr = root; curr != NULL; curr = curr->next) {
        printf("%d\n", curr->x);
    }

    return 0;
}
4

There are 4 best solutions below

0
John Bayko On

Leta give these things values for clarity.

In main() lets say the stack starts at address 100000. Local variable root is allocated there, so its address is 1000000.

malloc() returns something from the heap, lets say it's address 100, so root contains 100.

You call insert_end(), the parameters are put on the stack, so there's a new version of root local to insert_end(). Say main() needed a total of 8 bytes for local storage, and stack storage is extended downward (decreasing addresses) so the parameters for insert_end() are allocated at addresses 99992 (first parameter root) and 99988 (second parameter value).

root in main() and root in insert_end() are different variables with different contents. The content of root in inssert_end() is the address of root in main(), which is 100000.

Down below, you're assigning *root to curr, so you're following the address 100000 in root in insert_end() to root in main() and taking that value - 100. You're assigning 100 into curr, so curr references the memory you originally got from malloc().

To understand better, consider the type of root is Node **, when you add a * to a pointer variable, you remove it from the type, so *root is of type Node *, and that's the same as Node *curr.

0
narguis On

In this line on the insert_end() function

Node*curr=*root;

root is already a memory address, so it would be redundant to initialize it using an &, although you can do it.

But when I set a double pointer and a pointer and I initialize the second one with *p, where p is the double pointer, I get a compile error and the program doesn't even run.

I don't really understand what you mean by that, but if you mean using

Node**curr=*root;

See, on your first example, root is the address of the head of your linked list, of type Node** (pointer to a pointer). using *root dereferences that pointer and now we have a pointer to a single node, of type Node*. On your assignment, curr is now a pointer to a node that points to the same node as *root.

On the second example, curr would be a pointer to a pointer of a node, that is, a Node**, in simpler words, it would point the address of a node. *root, on the other hand, would be a Node*, that is, it would point to a node itself.

It's easy to see how this would cast an error because of the type mismatch, and this is probably what's generating your compile error.

Your post was a bit confusing, so i don't know if this is the answer you were looking for, you should be a little more clear and provide some example of the errors you're getting next time!

0
chqrlie On

The argument root in insert_end has type Node **root: it is a pointer to a Node pointer. It is a tad confusing to use the same name root for this thing in insert_end and in main where root is a Node * pointing to the first node of the list.

Node *curr defines a pointer to iterate on the list and find the last Node in order to append the newly allocate node as the next element of said last node. You must use curr = *root to start the iteration at the first node.

Note however that one should test if curr is a null pointer to avoid undefined behavior when testing curr->next on an empty list.

Here is a modified version with more explicit names:

#include <stdio.h>
#include <stdlib.h>
   
typedef struct Node {
    int x;
    struct Node *next;
} Node;

void append_node(Node **root_p, int value) {

    Node *new_node = malloc(sizeof(Node));
    if (new_node == NULL) {
        printf("allocation error\n");
        return;
    }
    new_node->x = value;
    new_node->next = NULL;

    Node *curr = *root_p;
    if (curr == NULL) {
        // list is empty
        *root_p = new_node;
    } else {
        // list is not empty: find the last node
        while (curr->next != NULL) {
            curr = curr->next;
        }
        curr->next = new_node;
    }
}

int main(void) {
    // start with an empty list
    Node *root = NULL;

    // append 2 nodes
    append_node(&root, 15);
    append_node(&root, -2);

    // print node values
    for (Node *curr = root; curr != NULL; curr = curr->next) {
        printf("%d\n", curr->x);
    }
    // it does not matter the order of freeing the memory
    while (root != NULL) {
        Node *curr = root;
        root = curr->next;
        free(curr);
    }
    return 0;
}
0
arfneto On

I don't understand why I don't get a bug when I initialize a pointer with *p1, where p1 is a double pointer

Well, a double pointer is a pointer to a pointer. If p1 is such a thing, a double pointer, then *p1 is a pointer. This is C. If *p1 is a pointer, why would it be a problem to initialize a pointer with its value?

if symbol is char**** then

  • *symbol is char***
  • **symbol is char**
  • ***symbol is char*
  • ****symbol is a char

It is dereferencing...

example

This program changes the value of the int in Node using pointers to pointers:

#include <stdio.h>

typedef struct
{
    int x;
} Node;

int main(void)
{
    Node one = {1};

    Node*    pNode    = &one;
    Node**   ppNode   = &pNode;
    Node***  pppNode  = &ppNode;
    Node**** ppppNode = &pppNode;

    printf("value in node is %d\n", one.x);
    pNode->x = 42;
    printf("value in node is %d\n", one.x);
    (**ppNode).x = 43;
    printf("value in node is %d\n", one.x);
    (***pppNode).x = 45;
    printf("value in node is %d\n", one.x);
    (****ppppNode).x = 46;
    printf("value in node is %d\n", one.x);

    (****ppppNode).x = 47; // ****ppppNode is Node
    printf("value in node is %d\n", one.x);

    (***ppppNode)->x = 48; // ***ppppNode is Node*
    printf("value in node is %d\n", one.x);
    return 0;
}

output

value in node is 1
value in node is 42
value in node is 43
value in node is 45
value in node is 46
value in node is 47
value in node is 48

It may help to understand that in C you declare a name as a type. If p1 is a pointer to a pointer to Node then you declare p1. A name. As being of a type: Node**.You do not declare **p1. The type is Node**. So the declaration is

    Node**                          p1;

because it is p1 the name you are declaring and Node** the type, as the compiler would tell you. For the compiler the spaces are irrelevant.

If p1 is Node** then

  • *p1 is Node*
  • **p1 is Node

It is the pointer being dereferenced. It is C. But it is not what you are declaring. You are declaring a name as a type. The name is p1. The type is Node**. So

    Node                            **p1;

Is ok but it is more of a consequence of p1 being of the type is is. It makes you mentally de-reference the pointer as you read the declaration.