Issue removing a node from a linked list - c

Since the code was cryptic I decided to reformulate it.
This code is trying to remove the second element from a linked list (the node with number 2 on "int data"). The first parameter of remove_node is the address of the first pointer of the list, so if I have to remove the first pointer of the list I am able to start the list from the next pointer.
The problem is inside the second while loop, inside the if clause, more specifically with the free(previous->next) function, it is changing the address pointed by address_of_ptr (aka *address_of_ptr) and I can't understand why this is happening. Could someone enlighten me?
#include <stdlib.h>
typedef struct node
{
struct node *next;
int data;
} node;
void remove_node(node **address_of_ptr, int int_to_remove)
{
node *previous;
while (*address_of_ptr != NULL && (*address_of_ptr)->data == int_to_remove)
{
previous = *address_of_ptr;
*address_of_ptr = (*address_of_ptr)->next;
free(previous);
}
previous = *address_of_ptr;
address_of_ptr = &((*address_of_ptr)->next);
while (*address_of_ptr != NULL)
{
if ((*address_of_ptr)->data == int_to_remove)
{
address_of_ptr = &((*address_of_ptr)->next);
free(previous->next);
previous->next = *address_of_ptr;
/*or
previous->next = (*address_of_pointer)->next;
free(*address_of_pointer);
address_of_pointer = &previous->next;
*/
}
else
{
previous = *address_of_ptr;
address_of_ptr = &((*address_of_ptr)->next);
}
}
}
int main(void)
{
node *head = malloc(sizeof(node));
node *a = malloc(sizeof(node));
node *b = malloc(sizeof(node));
head->next = a;
a->next = b;
b->next = NULL;
head->data = 1;
a->data = 2;
b->data = 1;
remove_node(&head, 2);
return 0;
}
I figured it out using pythontutor.com.
Here is an image of the execution about to replace address_of_ptr with the address of the next pointer in the list:
about to replace address_of_ptr
In my mind what would happen is that address_of_ptr would move from head to a:
like this
But what actually happens is this:
address_of_ptr move from head to the first node's next variable
Which is also the address of the next node (such as a).
Inside the if clause:
address_of_ptr is set to the second node's next variable
What I was expecting was it being set to b:
Like this
Since it becomes equivalent to previous->next, it causes program to free address_of_ptr.

I would like to thank everyone who helped me sorting this issue, I truly appreciate your effort in making the explanation clear as possible. After a lot of thinking a solution that I believe is much simpler than my first code come up. #David C. Rankin mentioned "Linus on Understanding Pointers", and I think "I might have grasped" what Linus was referring to:
void remove_node(node **address_of_ptr, int int_to_remove)
{
node *to_remove;
while (*address_of_ptr != NULL)
{
if ((*address_of_ptr)->data == int_to_remove)
{
to_remove = *address_of_ptr;
*address_of_ptr = (*address_of_ptr)->next;
free(to_remove);
}
else
{
address_of_ptr = &(*address_of_ptr)->next;
}
}
}

First of all, if the first while loop stops due to *address_of_ptr == NULL, then the line
address_of_ptr = &((*address_of_ptr)->next);
will cause undefined behavior, due to deferencing a NULL pointer. However, this will not happen with the sample input provided by the function main, so it is not the cause of your problem.
Your problem is the following:
When first executing inside the if block of the second while loop (which will be in the first iteration of the second while loop with your sample input), address_of_ptr will point to the next member of the first node in the linked list, so the pointer will have the value of &head->next. This is because you have the line
address_of_ptr = &((*address_of_ptr)->next);
in several places in your program, and it will execute one of these lines exactly once by the time you enter the if block.
The first line of that if block is also the line
address_of_ptr = &((*address_of_ptr)->next);
so that after executing that line, address_of_ptr will point to the next member of the second node in the linked list, so the pointer will have the value of &a->next.
At that time, the value of previous will be head, because at the time
previous = *address_of_ptr;
was executed, address_of_ptr had the value of &head.
Therefore, when the next line
free(previous->next);
is executed, previous->next will have the value of a, which ends the lifetime of that node. As already stated, at this time address_of_ptr will have the value &a->next, which means that address_of_ptr is now a dangling pointer, because it is now pointing to a freed memory location. Therefore, it is not surprising that *address_of_ptr changes after the free statement, because freed memory can change at any time.
Note that the function remove_node can be implemented in a much simpler way:
void remove_node( node **pp_head, int int_to_remove )
{
node **pp = pp_head, *p;
//process one node per loop iteration
while ( (p = *pp) != NULL )
{
//check if node should be removed
if ( p->data == int_to_remove )
{
//unlink the node from the linked list
*pp = p->next;
//free the unlinked node
free( p );
}
else
{
//go to next node in the list
pp = &p->next;
}
}
}

This line:
begin_list = &previous->next;
You might be mis-thinking how pointers to pointers work. Since begin_list is a t_list **, you generally want to assign to *begin_list. Also, since previous->next is already an address, you don't want to assign the address to *begin_list, just the value, so this should work instead:
*begin_list = previous->next;
Same for the similar line below it.

I prepared an answer last night several minutes after you asked the question at first, and put a lot of thought into it, but didn't come back until just now. I'd let my version go, but it's a more detailed look into the problem anyway, so I thought I'd include it for posterity.
The problem lay on line 36:
begin_list = &(*begin_list)->next;
In the first while loop in the function ft_list_remove_if, I can see that you wanted to change the beginning of the list to point to some list item potentially later in the list; so for example, if you wanted to remove all items in the list which contained the string "a", this function should indeed change the value of list1 in the main function; this way the new start of the list would actually be list2.
However, by the time the second while loop is entered, there's no need to change the pointer to the start of the list anymore; that was taken care of in the first while loop. As such, begin_list should really not be used anymore; it is a pointer to a pointer to a list item, so it references the variable which points to a list item. This is not desirable. Let's step through the program line-by-line and I'll show you what I mean.
Upon entering the function ft_list_remove_if, we run past the first while loop since the first element of the list does not contain the data "b":
while (*begin_list != 0 && (*cmp)((*begin_list)->data, data_ref) == 0)
{
[...]
}
We're still on the first list item when we enter the second while loop, and the first item does not match. We therefore go to the else branch of the if statement on line 25:
if ((*cmp)((*begin_list)->data, data_ref) == 0)
{
[...]
}
else
{
previous = *begin_list;
begin_list = &(*begin_list)->next;
}
In the else branch, previous is set to the first item in the linked list. Fair enough. Then begin_list is set to the address of the next member of the first list item. This means that when the first list item's next member changes, so does the value of *begin_list. Let that point sink in.
Now we go on to the next iteration of the while loop. This time *begin_list points to the second list item, which does indeed match the search criteria, so we enter the main if branch instead of the else:
if ((*cmp)((*begin_list)->data, data_ref) == 0)
{
previous->next = (*begin_list)->next;
free(*begin_list);
begin_list = &previous->next;
}
OK. So let's remember here: previous is set to the first item in the linked list. *begin_list is set to the second item in the linked list. But what is begin_list set to? In short, the address of previous->next. Remember the following from the last cycle of the loop?
previous = *begin_list;
begin_list = &(*begin_list)->next;
So now when previous->next is changed on line 28 to the third item in the list, *begin_list is also changed to the third item in the list! That means that line 29 frees the memory allocated to the third item in the list, while the second item no longer referenced by any pointers! By sheer luck, the program might continue to look like it functions properly for the next few steps, but it doesn't; the third element has been freed, and the contents of the third element get scrambled up a little bit and the third element's next and data members are filled with invalid data, resulting in the segfault.
However, the procedure worked when you copied *begin_list to to_remove first, and then freed the data pointed to by to_remove:
to_remove = *begin_list;
previous->next = (*begin_list)->next;
free(to_remove);
begin_list = &previous->next;
This is because to_remove continues to point to the second element, even after *begin_list has been changed by the previous->next = (*begin_list)->next; statement; thus the second element is freed as it should be, and on line 31 begin_list is set to the address of the third element, just like it should be.
That doesn't mean that the latter is a good solution. Really, I think begin_list shouldn't be used at all beyond the first while loop, since it doesn't have to be. My suggestion is to use another pointer named current with which you can step through the list:
#include <stdlib.h>
#include <stdio.h>
typedef struct s_list
{
struct s_list *next;
void *data;
} t_list;
void ft_list_remove_if(t_list **begin_list, void *data_ref, int (*cmp)())
{
t_list *previous;
t_list *current;
if (begin_list == 0)
return;
while (*begin_list != 0 && (*cmp)((*begin_list)->data, data_ref) == 0)
{
previous = *begin_list;
*begin_list = (*begin_list)->next;
free(previous);
}
current = *begin_list;
while (current != 0)
{
if ((*cmp)(current->data, data_ref) == 0)
{
previous->next = current->next;
free(current);
current = previous->next;
}
else
{
previous = current;
current = current->next;
}
}
}
int ft_strcmp(char *s1, char *s2)
{
int i;
i = 0;
while (s1[i] == s2[i] && s1[i] != '\0')
++i;
return (s1[i] - s2[i]);
}
int main(void)
{
t_list *list1 = malloc(sizeof(*list1));
t_list *list2 = malloc(sizeof(*list2));
t_list *list3 = malloc(sizeof(*list3));
list1->data = "a";
list2->data = "b";
list3->data = "a";
list1->next = list2;
list2->next = list3;
list3->next = 0;
ft_list_remove_if(&list1, "b", &ft_strcmp);
return (0);
}

Related

Linked list only prints out the last added member. What could be the mistake? [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I'm having a problem with a one way linked list. I'm trying to add elements to the end of the list(I guess it's FIFO arrangement) inside of a function (not passing elements one by one from the main function) so my idea was to save the position of the head pointer and create another pointer which would change positions every time I added an element.
The problem is that the head pointer's value also changes when I add a new element and I'm not sure why, because the only time I changed anything in regards to the head pointer is when I added the first element to the list. Then created the "current" pointer which I assigned to the head pointer and used from then on to add all other elements, but when I tried printing the head pointer value in each iteration of the while loop, it printed the last entered value (the value I wanted to assign to "current", not head).
When I try to print out the whole list, it only prints out the last element over and over.
Here's the code I've written:
typedef struct node NODE;
typedef struct node* PNODE;
struct node {
int info;
PNODE next;
};
void addToEnd(PNODE *head) {
PNODE newN = malloc(sizeof(NODE));
int num = 0;
printf("Type in the numbers. The loop ends when you type in -1.\n");
scanf("%d", &num);
getchar();
newN->info = num;
newN->next = NULL;
(*head) = newN;
PNODE current = *head;
while(1) {
scanf("%d", &num);
getchar();
if (num == -1) {
break;
}
newN->info = num;
newN->next = NULL;
if (current->next == NULL) {
current->next = newN;
current = current->next;
}
}
current = (*head);
while (current != NULL) {
printf("%d ", current->info);
current = current->next;
}
}
main() {
PNODE head = NULL;
addToEnd(&head);
}
The problem is that the head pointer's value also changes when I add a new element and I'm not sure why, because the only time I changed anything in regards to the head pointer is when I added the first element to the list.
By "the head node's value" I take you to mean (*head)->info (as opposed to *head).
It is true that you assign a value to *head only once, but you update the value of (*head)->info multiple times, as a result of *head, newN, and current all having the same (pointer) value. Observe:
(*head) = newN;
PNODE current = *head;
... *head is assigned the value of newN and current is assigned the value of *head; now these are all the same. Then ...
newN->info = num;
newN->next = NULL;
... current, newN, and *head are all still the same, so this sets the info and next members of the node to which they all point. Then, because current == newN, we know that current->next == newN->next, which was just set to NULL, so this is always executed:
if (current->next == NULL) {
current->next = newN;
current = current->next;
Moreover, because current and newN (and *head) are still equal, current->next = newN is equivalent to current->next = current, and of course, after that, current = current->next does not change the value of current or make it unequal to *head or newN. The three remain the same at all times throughout the input loop.
After that, of course ...
current = (*head);
... still leaves the three pointers all equal to each other. If at least one number was entered during the input loop then we also have current->next == current, so the print loop will print the last value entered over and over, until you kill the program.
In short, it's pretty much all wrong.
If you want to add elements to a list such that each one is appended at the end, then a reasonable way to proceed is this:
PREPARATION
Create a dummy NODE and assign *phead as the value of its next pointer. (Just declare a NODE normally; this one doesn't need to be dynamically allocated.)
Initialize current to point to the dummy node (current = &dummy).
Use a loop to advance current to point to the last node in the list (it will continue to point to dummy if the list is initially empty).
INPUT
Attempt to read a value. Break from the loop if none is presented; otherwise ...
Allocate a new NODE.
Assign the newly read value to the new node.
Update current->next to point to the new node.
Update current to point to the new node. (Do not do this before step 4!)
Go back to INPUT.1.
FINISHING UP
After loop termination, current points to the last node added. Set current->next to NULL to mark the end of the list.
Set *head = dummy->next. If the list was initially empty and at least one value was added, then this will assign *head to point to the head node. Otherwise, it will just copy the original value of *head back into it (see step PREPARATION.1).

Linked lists and pointers confusion

I'm working on my final project and I was introduced to linked lists, which I must use.
I'm incredibly frustrated after trying to understand how the code works. The concept to me makes complete sense. The code i'm given as an example though, doesn't.
typedef struct node_s {
char name[20];
int age;
struct node_s *listp;
} node;
while (!feof(inp)) {
temp = (node *)malloc(sizeof(node)); // creation of memory
fscanf(inp, "%s%d", temp->name, &temp->age);
if (head == NULL)
head = temp; // setting the head of the list
else {
tail->listp = temp; // else connecting to previous element
}
tail = temp; // updating the current element
tail->listp = NULL; // setting pointer to null.
}
I'm confused at how tail->listp will point to the second element, when each time it's set to be NULL. To further illustrate my confusion, in the else statement tail->listp will point to the new element, which is understandable.
But at the end we point tail->listp to NULL which just disregard the else statement. Yet the code works just fine, and here I am, extremely confused.
You're missing the statement before, which is
tail = temp; // updating the current element
In a loop, you create a new element temp, and link it onto the list. If it's the first element, you start the list by setting it to both the head and the tail, essentially. If it's not the first element, you link it onto the end of the list.
tail->listp = temp;
Then, you set tail=temp to update the pointer to the end of the list, and make sure that the element at the end of the list is pointing to null
tail->listp = NULL;
You could also do
temp->listp = NULL;
tail=temp;
which would be equivalent, if my eyes don't fail me.

C - Linked Lists

I am trying to understand the code of linked lists. I understand how they work.
I am looking at some code to do with dynamic memory and linked lists, I have simplified it here:
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
char *word;
struct node *next;
} node;
void display_word(node *start) {
node *start_node = start;
puts("");
for(; start_node != NULL; start_node = start_node->next) {
printf("%s", start_node->word);
}
}
node* create_node(char *input) {
node *n = malloc(sizeof(node));;
n->word = strdup(input);
n->next = NULL;
return n;
}
int main() {
node *start_node = NULL;
node *n = NULL;
node *next_node = NULL;
char word_holder[20];
for(; fgets(word_holder,80,stdin) != NULL; n = next_node) {
next_node = create_node(word_holder);
if(start_node == NULL)
start_node = next_node;
if(n != NULL)
n->next = next_node;
}
display_word(start);
}
So the program creates a linked list of each word the user enters and then it prints it out.
What I dont understand is in the main() function where next_node is assigned to a new node everytime to create a new one, but start_node points to next_node, so it will point to every new node that next_node creates each time? So how is it possible to still keep the list? Shouldn't we lose the old node each time?
Can someone explain please.
When the first node is created, a pointer to it is saved in start.
When subsequent nodes are created, they are added at the end of the list, so start still points to the first node, and through it, the rest of the list.
Step through the code with a debugger, or get out a pencil and paper and draw what's happening as you step through in your brain, and you'll see how it all gets put together.
When the first node is created, a pointer to it is saved in start.
After every iteration of the loop, "n" is set to the node just created, because the last piece of the for loop (;n = next) is executed after every iteration of the loop. So mid loop execution "n" will always be pointing to the previous node. Therefore the statement n->next = next is setting the previous node's "next" pointer to the new node.
So during the second iteration of the loop, n = start, and start->next is set to "next" the node you just created.
I hope this answers your question - every time you are updating "next", you're setting that to be yet another new node. Each node has their own "next" that leads to the next node, so you aren't going to lose anything by doing it this way. I didn't actually test your code but since "Start" points to the first node always, you aren't going to lose any nodes along the way. A debugger should help if you're curious to learn more about how this works!

Deleting first node in linked list in C

I know that this was answered many times, but I just can't figure out what is wrong.
I have a structure
typedef struct zaznam{
char kategoria[53];
char znacka[53];
char predajca[103];
double cena;
int rok;
char stav[203];
struct zaznam *next;
}ZAZNAM;
And I want to delete first node this way.
for(prev = act = prvy; act!=NULL; prev=act, act=act->next){
if((strstr(act->znacka,vyber)!=NULL)){
if(act->znacka==prvy->znacka){
//if "znacka" of the actual node is equal to the first
prev=prvy->next; //in "previous" is pointer to the second node
free((void *)act); //free first node
prvy=prev; //first node = previous
}
else{ //this works
prev->next=act->next;
free((void *)act);
act=prev;
}
And it works for everything but not for the first node.
Well, you violate your own invariant prev->next == act already in the beginning: for (prev = act = prvy;...
Second, this act->znacka==prvy->znacka should be act==prvy to find out whether you are at the beginning of the chain, otherwise it confuses people.
And I would probably try to reestablish you starting (but wrong) invariant (which is act==prev) by adding act=prev; for the first case. Maybe it will work.
Looks to me like when you delete the first node you're not properly assigning prev to point to the next node when you continue on the next iteration of the for-loop. For instance, in your first if statement to check if you're at the first node, you do the following:
prev=prvy->next; //in "previous" is pointer to the second node
free((void *)act); //free first node
prvy=prev; //first node = previous
That does free the first node, but, immediately in your for-loop you then assign act to prev here:
for(prev = act = prvy; act!=NULL; prev=act, act=act->next)
Since freeing memory doesn't actually erase the contents of memory, you proceed down the rest of the list as if the first node had not been freed. Actually using a freed pointer like that is undefined behavior, so anything could happen (i.e. you could crash, etc. ... in this case it seems like you aren't).
Try changing your code to the following:
for(prev = act = prvy; act!=NULL;)
{
if((strstr(act->znacka,vyber)!=NULL))
{
if(act==prvy)
{
//we're at the first node
ZAZNAM* temp=act->next;
free((void *)act); //free first node
prev=act=prvy=temp; //re-establish starting condition
}
else
{
prev->next=act->next;
free((void *)act);
act=prev->next;
}
}
else
{
//iterate here because if you delete the first node, you don't want
//to start iterating like would happen if you kept iteration in the
//for-loop declaration
prev=act;
act=act->next;
}
}
why do you have a loop. Deleting the first entry in a list is just a matter of
tmp = head->next
delete head
head = tmp

C Linked list only contains first element...not sure what happens to rest

I posted a question a few days ago about a linked list in C. I thought everything was ok then the prof emails us saying that instead of this signature:
int insert_intlist( INTLIST* lst, int n); /* Inserts an int (n) into an intlist from the beginning*/
He accidentally meant:
int insert_intlist( INTLIST** lst, int n); /* Inserts an int (n) into an intlist from the beginning*/
I thought to myself cool now that I have a pointer to a pointer I can move the pointer outside of main and when I return to main I will still have my complete linked list.
He starts off with giving us this:
INTLIST* init_intlist( int n )
{
INTLIST *lst; //pointer to store node
lst = (INTLIST *)malloc(sizeof(INTLIST)); //create enough memory for the node
lst->datum = n; //set the value
lst->next = NULL; //set the pointer
return lst; //return the new list
}
Which is simply to initialize the list like so in main:
if (lst==NULL)
lst = init_intlist(i);
else
insert_intlist(lst, i);
lst is of type INTLIST* so its defined as INTLIST* lst. So I read in some numbers from a text file like 1 3 4 9.
It is supposed to create a linked list from this...so the first number would go to init_intlist(1); And that was defined above. Then it grabs the next number 3 in this case and calls insert_intlist(lst, 3). Well here is my insert_intlist and all I want to do is insert at the beginning of the list:
int insert_intlist(INTLIST** lst, int n )
{
INTLIST* lstTemp; //pointer to store temporary node to be added to linked list
lstTemp = (INTLIST *)malloc(sizeof(INTLIST)); //create enough memory for the node
lstTemp->datum = n; //assign the value
//check if there is anything in the list,
//there should be, but just in case
if(*lst == NULL)
{
*lst=lstTemp;
lstTemp->next=NULL;
}
else
{
lstTemp->next = *lst; //attach new node to the front
*lst = lstTemp; //incoming new node becomes the head of the list
}
return 0;
}
So if the list contained 1 initially this function would simply create a new node and then make this temp node->next point to the head of the list (which I thought was lst) and then reassign the head of the list to this new temp node.
It all looks like it is running right but when I try to print my list to the screen it only prints the number 1.
Anyone have any clues as to what I am doing wrong?
You're being passed a pointer to a pointer. You want to alter the pointer that is being pointed to, not the pointer to a pointer itself. Does that make sense?
if(lst == NULL)
Here you're checking to see if you were passed a NULL pointer. Good practice for error checking, but not for what you were doing right there. If lst is NULL, then you don't even have a pointer to a pointer, can't do anything, and should return a nonzero error code without doing anything else.
Once you're sure your pointer is not NULL, then you look at the pointer it's pointing to (*lst). The pointed-to pointer is the pointer to the first list item. If that pointer is NULL, then you change it to the new item's pointer. Basically, where you use lst you should be using *lst or (*lst). (REMEMBER: the * operator runs after the -> operator! So to get a field in the object pointed to by the pointer that is pointed to by lst [pant, pant], you use (*lst)->whatever.)
P.S. This kind of pointer work is critical to learn to be a good programmer, especially with C.
P.P.S. The other thing you got wrong is that instead of
insert_intlist(lst, i);
you're supposed to call it like
insert_intlist(&lst, i);
... and, for brownie points, check the return code for errors.
The first problem that pops into my mind is that in insert_intlist() you're doing lst = lstTemp;. This should be *lst = lstTemp;. This way you assign to the list pointer that you were supplied rather than to the list pointer pointer (which does not update anything outside of the function).
Since you are using a pointer to another pointer in the insert function, you can change at which memory location the latter actually points to. I changed the insert code a bit and it works fine:
int insert_intlist(INTLIST** lst, int n )
{
INTLIST* lstTemp; //pointer to store temporary node to be added to linked list
lstTemp = (INTLIST *)malloc(sizeof(INTLIST)); //create enough memory for the node
lstTemp->datum = n; //assign the value
//check if there is anything in the list,
//there should be, but just in case
if(*lst == NULL)
{
lstTemp->next=NULL;
*lst = lstTemp;
}
else
{
lstTemp->next = *lst; //attach new node to the front
*lst = lstTemp; //incoming new node becomes the head of the list
}
return 0;
}
EDIT: I see you edited your question. In that case, maybe you are calling the insert function in the wrong way in main(). Try this
int main()
{
INTLIST *head;
head = init_intlist(42);
insert_intlist(&head, 41);
display(head);
return 0;
}
Check to see if lstTemp->next == NULL after lstTemp->next=lst;

Resources