0

So I'm writing a program that creates a dinamic list, it inserts a new node after the last node and eventually it prints the freshly created list. I want that when I print it it actually prints the number of the current node in succession (like Info: 1, Info: 2, Info: 3) but it turns out it prints always the last node. I guess it's a pointer error, but can't really find what.

#include <stdio.h>
#include <stdlib.h>

struct node{
  int info;
  struct node* pNext;
};

typedef struct node Node;

void tail_insertion(Node* pFirst, Node* pLast);
void print_list(Node* pFirst);

static int n=3;

int main()
{
    Node *pFirst=NULL, *pLast=NULL;
    tail_insertion(pFirst, pLast);
}

void tail_insertion(Node* pFirst, Node* pLast){
  // Creation of a new node in the heap
  Node *pNew = (Node*) malloc(sizeof(Node));
  pNew->info=0;
  pNew->pNext= NULL;

  for(int i=0; i<3;i++){
    if(pFirst == NULL){// No node in the list
      pNew->info++;
      pFirst = pNew; // The first node is the newly created one
      pLast = pNew; // The last node is the newly created one
      printf("Ok the list was empty\n");
    }
    else{
      // Else, there is already at least one node in the list
      pNew->info++;
      pLast-> pNext= pNew; // the last node becomes the second one
      pLast= pNew; // The last node is the newly created one
      printf("Ok the list wasn't empty\n");
    }
  }
  print_list(pFirst);
  return;
}


void print_list(Node* pFirst){
  if(pFirst == NULL){
    printf("No node in the list!\n");
  }
  else{
    // New pointer used to scan the list.
    Node* pScan = pFirst;
    do{
      printf("Info: %d\n", pScan->info);
      // ptrScan is updated to point to the next node in the
      // list
      pScan = pScan->pNext;
    }while(pScan!= NULL && --n); //NULL when this was the last node
  }
  return;
}

It prints so:

Ok the list was empty
Ok the list wasn't empty
Ok the list wasn't empty
Info: 3
Info: 3
Info: 3
Luca
  • 21
  • 7
  • 3
    Hint: The value of `pFirst` *will not* be changed outside of the function. – Eugene Sh. Feb 15 '19 at 19:04
  • @EugeneSh. What should I do then? – Luca Feb 15 '19 at 19:06
  • Why do you need `pLast`? Seems redundant and not checked for errors? – Neil Feb 15 '19 at 19:08
  • @NeilEdelman I use that to point at end of the list so I know where to insert the new node – Luca Feb 15 '19 at 19:10
  • 1
    in your **struct node** you have a node element which points to the next node variable in your linked list, you could use that to find the end of the list. –  Feb 15 '19 at 19:12
  • @user10835998 You're true! – Luca Feb 15 '19 at 19:14
  • Hint: when you do pFirst = pNew you let pFirst point to pNew –  Feb 15 '19 at 19:19
  • You could have a pointer to a pointer to Node and `tail_insertion(&pFirst);`. `C` is pass-by-value, https://stackoverflow.com/questions/2229498/passing-by-reference-in-c – Neil Feb 15 '19 at 19:22
  • another hint: you could print the address a pointer is pointing to like so: `printf("pNew address: %p\n", pNew);` –  Feb 15 '19 at 19:33
  • Thanks for the hints guys – Luca Feb 15 '19 at 19:52
  • If you have a structure that requires `pFirst` and `pLast` to always be passed together, you should think about putting them in a single `struct`. – Neil Feb 15 '19 at 22:37
  • OT: regarding: `Node *pNew = (Node*) malloc(sizeof(Node));` in C, the returned type from any of the heap allocation functions: `malloc` `calloc` `realloc` is `void*` which can be assigned to any pointer. Casting just clutters the code, making it more difficult to understand, debug, etc. 2) when calling any of these functions, always check (!=NULL) the returned value to assure the operation was successful. If not successful, call `perror()` with your error message, cleanup, then call `exit( EXIT_FAILURE );` Note: `exit()` and `EXIT_FAILURE` are from the header file: `stdlib.h` – user3629249 Feb 17 '19 at 10:21

2 Answers2

2

Code fix

Using you own code, you should fix tail_insertion(...) to be like the code below. There are comments to help understand the changes.

Node *pNew; /* Declaring pNew *without* assignment. */

for(int i = 0; i < 3; i++){
    
    pNew = (Node*) malloc(sizeof(Node)); /* Allocation of new memory address. */
    
    pNew->info  = i; /* pNew->info works like an ID in you current code. */

    pNew->pNext = NULL; /* It is proper to assign NULL */
    
    if(pFirst == NULL){
        
        pNew->info++;
        
        pFirst       = pNew;
        pLast        = pNew;
        
        printf("The list was empty.\n");
        
    } else {
        
        pNew->info++;
        
        pLast->pNext = pNew;
        pLast        = pNew;
        
        printf("The list wasn't empty.\n");
        
    }
}

print_list(pFirst);

Note that I have removed the return(...) function since it wasn't necessary. It is not necessary because tail_insertion(...) has the type void.

Also, pNew->info is storing the ID of the node as of my reading. So it shouldn't be a problem assigning i to it. If necessary, make the proper changes.

Now.. Let's begin explaining things from the beginning!

General Code Analysis

Let's consider tail_insertion(...) and what you want it to do:

  • You create a new node;
  • You give it the right values;
  • You start a for loop with the intention of creating more nodes based on certain conditions;
  • You print the whole list to check if it worked;

Now, look at your code for tail_insertion(...) function (comments removed):

Node *pNew = (Node*) malloc(sizeof(Node));

pNew->info=0;
pNew->pNext= NULL;

for(int i=0; i<3;i++){
    
    if(pFirst == NULL){
        
        pNew->info++;

        pFirst = pNew;
        pLast = pNew;
        
        printf("Ok, the list was empty.\n");
        
    } else {
        
        pNew->info++;

        pLast-> pNext= pNew;
        pLast= pNew;
        
        printf("Ok, the list wasn't empty.\n");
        
    }
    
}

print_list(pFirst);

Seeing any problem yet? No? Well, let's proceed.

Debugging

We are going to debug this function step-by-step. Aside from program fixes, it is important for the learning process as well:

  • Creating a node:
    • You create pNEW. It is empty;
    • Let's give it the address 0x01 (fictious);
    • A 0 is assigned to pNew->info;
    • A NULL is assigned to pNew->pNext;
  • Beginning of the for loop - an attempt to create 3 nodes for a list:
    • i = 0:
      • You check to see if the list is empty. It is:
        • You add 1 to pNew->info value. The address of pNew is 0x01;
        • pFirst is assigned to point at the address 0x01;
        • pLast is assigned to point at the address 0x01 since the list was empty;
        • You print a message saying the list was empty;
    • At the end of i = 0, the list is as follows:
      • pFirst points at the address 0x01;
      • pLast points at the address 0x01;
      • 0x01->info is 1;
    • i = 1
      • You check to see if the list is empty. It is not:
        • You add 1 to pNew->info value. The address of pNew is 0x01. There was never a creation of another node;
        • pLast->pNext is assigned to point at the address 0x01;
        • pLast is assigned to point at the address 0x01;
        • You print a message saying the list wasn't empty;
    • At the end of i = 1, the list is as follows:
      • pFirst points at the address 0x01;
      • pLast points at the address 0x01;
      • pLast->pNext points at the address 0x01;
      • 0x01->info is 2;
    • i = 2
      • You check to see if the list is empty. It is not:
        • You add 1 to pNew->info value. The address of pNew is 0x01. There was never a creation of another node;
        • pLast->pNext is assigned to point at the address 0x01;
        • pLast is assigned to point at the address 0x01;
        • You print a message saying the list wasn't empty;
    • At the end of i = 2, the list is as follows:
      • pFirst points at the address 0x01;
      • pLast points at the address 0x01;
      • pLast->pNext points at the address 0x01;
      • pLast->pNext->pNext points at the address 0x01;
      • 0x01->info is 3;
    • End of the for loop;
  • Printing the resulting list:
    • A call for print_list(...)

Conclusion

As of now, you probably saw what's wrong: you haven't created a new node inside the for loop. You created it outside of the loop. This makes a repetitive process of adding the very same node with the same address to the list.

Also, you are having problems with the value of info. But that is thanks to using the same node as stated above. It will need just some adjusting in this incremental logic that sets info value to accommodate the modifications need to fix the usage of the same node and make everything work.

Final Notes

There are comments saying you don't need pLast. While that is true, you need to consider:

  • If you only use pFirst, you need to make sure that after inserting the new node in pFirst->pNext, you do a pFirst->pNext->pNext = NULL. This way you can know whenever you have reached the end of the list by comparing to NULL;
  • If you continue to use pLast, you can compare to pLast to know if the address is the last address inside the list. Obviously, you can also compare to NULL after my fix;
  • Considering tail insertions, having pLast facilitates things quite a bit and reduces CPU usage in the long run;

You probably know but you should put print_list(pFirst); inside the main(...) function to makes things more organized. Also, it makes my "code OCD" reach new levels of desperation! ;-)

José
  • 354
  • 5
  • 18
  • 1
    Another design decision I would contemplate is head insertions, which allow `pFirst` to be the Single-Source of Truth and allow `O(1)` insertion, basically a stack. – Neil Feb 15 '19 at 22:32
  • 1
    Thank you for your explanation, it's very exhaustive! I now got the flaw. – Luca Feb 17 '19 at 10:28
1

Two problems:

  • you are not passing in pointers to the list pointers (Node * -> Node **)
  • you are trying to insert the same Node three times (move malloc() into the loop)

I guess static int n = 3 and while ... && --n) have probably been added in a desperate attempt to "fix" the endless loop in print_list(), because pscan->pNext yielded pscan in the original code...

Fixed and cleaned up code:

#include <stdio.h>
#include <stdlib.h>

typedef struct node Node;
typedef struct node {
  int   info;
  Node* pNext;
} Node;

static void print_list(Node* pFirst) {
  // New pointer used to scan the list.
  Node* pScan = pFirst;
  while (pScan) {
    printf("Info: %d\n", pScan->info);
    // ptrScan is updated to point to the next node in the
    // list
    pScan = pScan->pNext;
  }
}

static void tail_insertion(Node** pFirst, Node** pLast) {
  for (int i=0; i<3; i++) {
    // Creation of a new node in the heap
    Node *pNew  = malloc(sizeof(Node));
    pNew->info  = i + 1;
    pNew->pNext = NULL;

    if (*pFirst == NULL) {    // No node in the list
      *pFirst = pNew;         // The first node is the newly created one
      *pLast  = pNew;         // The last node is the newly created one
      printf("Ok the list was empty\n");
    } else {                  // Else, there is already at least one node in the list
      (*pLast)->pNext = pNew; // the last node becomes the second one
      *pLast          = pNew; // The last node is the newly created one
      printf("Ok the list wasn't empty\n");
    }
  }
  print_list(*pFirst);
}

int main(int argc, char *argv[]) {
  Node *pFirst = NULL;
  Node *pLast  = NULL;
  tail_insertion(&pFirst, &pLast);
}

Test run:

$ gcc -Wall -Werror -o dummy dummy.c
$ ./dummy
Ok the list was empty
Ok the list wasn't empty
Ok the list wasn't empty
Info: 1
Info: 2
Info: 3
Stefan Becker
  • 5,695
  • 9
  • 20
  • 30