Search code examples
cpointerslinked-listdouble-pointerpass-by-pointer

Deleting a list with pointer to pointer in C


The partial code, in C, is here:

typedef struct List {
  double v;
  struct List *next;
} List;

void deleteList (List **p) {
  *p = (*p)->next;
}

I am confused about how the deleteList function is working. So the arguement is a pointer to a pointer to a List structure. So we have:

p : pointer_2 --> pointer_1 --> List

So I have some questions:

  1. So what is *p in the function deleteList()? Is it pointer_1 or something else?
  2. Does *p before = mean the same as *p after the = sign?
  3. Is there a difference between *p and (*p) ?

Say we have:

... la --> lb --> lc --> ld ....

And say we want to delete lb. I get the idea, theoretically. You alter the la->next to point to lc. But I am confused about the pointer business. What is the argument to deleteList()? Is it, deleteList(la->next)? Or something else? And then the really confusing part. *p = ... is supposed to be la->next because this is the pointer we want to alter. But then ...(*p)->next, wouldn't this just be the lb? But we want lc? So it seems like *p have different meaning in the same line?!


Solution

  • Let;s at first write the function correctly.

    void deleteList( List **head ) 
    {
        while ( *head != NULL )
        {
            List *tmp = *head;
            *head = ( *head )->next;
            free( tmp ); 
        }
    }
    

    A pointer to the head node is passed to the function by reference.

    If you will define the function like

    void deleteList( List *head ) 
    {
        while ( head != NULL )
        {
            List *tmp = head;
            head = head->next;
            free( tmp ); 
        }
    }
    

    that is if the pointer will not be passed by reference then the function will deal with a copy of the pointer. Changing a copy does not influence on the original pointer.

    Consider the following demonstrative program.

    #include <stdio.h>
    #include <stdlib.h>
    
    void f( int *p )
    {
        p = NULL;
    }
    
    int main(void) 
    {
        int x = 10;
        int *px = &x;
    
        printf( "Before the function call px = %p\n", ( void * )px );   
    
        f( px );
    
        printf( "Adter  the function call px = %p\n", ( void * )px );   
    
        return 0;
    }
    

    Its output might look like

    Before the function call px = 0x7ffe26689a2c
    Adter  the function call px = 0x7ffe26689a2c
    

    That is the original pointer px was not changed because the function dealt with a copy of the pointer.

    To change the pointer you need to pass it to the function by reference

    #include <stdio.h>
    #include <stdlib.h>
    
    void f( int **p )
    {
        *p = NULL;
    }
    
    int main(void) 
    {
        int x = 10;
        int *px = &x;
    
        printf( "Before the function call px = %p\n", ( void * )px );   
    
        f( &px );
    
        printf( "Adter  the function call px = %p\n", ( void * )px );   
    
        return 0;
    }
    

    Now the program output might look like

    Before the function call px = 0x7ffed60815fc
    Adter  the function call px = (nil)
    

    Within the function you need to dereference the parameter to get the access to the passed by reference pointer.

    *p = NULL;
    

    ^^^^

    The same occurs in the function deleteNode. To check whether the passed pointer is equal to NULL there is used the following statement

    while ( *head != NULL )
           ^^^
    

    To access the data member next of the node pointed to by the origibal pointer you again have to dereference the parameter to get access to the original pointer

    *head
    

    So this expression yields the original pointer. So to access the data member next you have to write

    ( *head )->next
    

    You are using the parentheses because the postfix operator -> has a higher priority but you need at first to get the original pointer.

    That is if you had no a referenced pointer you would write

    head->next
    

    But when you have a referenced pointer that is when you have a pointer to an original pointer then to get the original pointer you have to derefernce the referenceing pointer like

    ( *head )->next
    

    You could write the function without accepting the pointer to the head node by reference. But in this case you should to add in the caller one more statement that will set the pointer head to NULL.

    For example

    void deleteList( List *head ) 
    {
        while ( head != NULL )
        {
            List *tmp = head;
            head = head->next;
            free( tmp ); 
        }
    }
    

    and in the caller you need to write

    List *head - NULL;
    
    // the code thatf fills the list
    
    deleteList( head );
    head = NULL;
    

    Or the function could return a null pointer like

    List * deleteList( List *head ) 
    {
        while ( head != NULL )
        {
            List *tmp = head;
            head = head->next;
            free( tmp ); 
        }
    
        return head;
    }
    

    and in the caller you could write

    List *head - NULL;
    
    // the code thatf fills the list
    
    head = deleteList( head );
    

    The advantage of defining the function that accepts the pointer to the head node by reference is that the user of the function does not need to remember to set the pointer to NULL by himself.