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.
Related
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);
}
I'm trying to just reverse a singly linked list, but with a bit of a twist. Rather than having the pointer to the next node be the actual next node, it points to the pointer in that next node.
struct _Node
{
union
{
int n;
char c;
} val;
void *ptr; /* points to ptr variable in next node, not beginning */
int var;
};
typedef struct _Node Node;
I know how to reverse a normal singly linked list and I think I have the general idea of how to go about solving this one, but I'm getting a segfault when I'm trying to access head->ptrand I don't know why.
Node *reverse(Node *head)
{
Node * temp;
Node * prev = NULL;
while(head != NULL)
{
temp = head->ptr + 4; /* add 4 to pass union and get beginning of next node */
head->ptr = prev;
prev = head;
head = temp;
}
return prev;
}
Even if I try and access head->ptr without adding 4, I get a segfault.
The driver that I have for this code is only an object file, so I can't see how things are being called or anything of the sort. I'm either missing something blatantly obvious or there is an issue in the driver.
First, I'll show you a major problem in your code:
while (head) // is shorter than while(head != NULL)
{
// Where does the 4 come from?
// And even if: You have to substract it.
// so, definitively a bug:
// temp = head->ptr + 4; /* add 4 to pass union and get beginning of next node */
size_t offset_ptr = (char*)head->ptr - (char*)head;
// the line above should be moved out of the while loop.
temp = head->ptr - offset_ptr;
Anyways, your algorithm probably won't work as written. If you want to reverse stuff, you are gonna have to work backwards (which is non-trivial in single linked lists). There are two options:
count the elements, allocate an array, remember the pointers in that array and then reassign the next pointers.
create a temporary double linked list (actually you only need another single reversely linked list, because both lists together form a double linked list). Then walk again to copy the next pointer from your temporary list to the old list. Remember to free the temporary list prior to returning.
I tried your code and did some tweaking, well in my opinion your code had some logical error. Your pointers were overwritten again and again (jumping from one node to another and back: 1->2 , 2->1) which were leading to suspected memory leaks. Here, a working version of your code...
Node *reverse(Node *head)
{
Node *temp = 0;
//Re-ordering of your assignment statements
while (head) //No need for explicit head != NULL
{
//Here this line ensures that pointers are not overwritten
Node *next = (Node *)head->ptr; //Type casting from void * to Node *
head->ptr = temp;
temp = head;
head = next;
}
return temp;
}
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!
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
I was trying to reverse a linked list, however whenever I execute the following function, I get only the last element. For example, if the list contained 11,12,13 earlier. After executing the function, it contains only 13. Kindly point out the bug in my code
void reverselist() {
struct node *a, *b, *c;
a = NULL;
b = c = start;
while (c != NULL) {
c = b->next;
b->next = a;
a = b;
b = c;
}
start = c;
}
Doesn't your loop guard insure that start is null?
If you aren't using start to identify the first element of the list, then the variable you ARE using is still pointing to what WAS the first element, which is now the last.
c is a helper pointer.
void reverselist()
{
struct node *a, *b, *c;
a=NULL;
b=start;
while(b!=NULL)
{
c=b->next
b->next=a;
a=b
b=c
}
start=a;
}
// You should assume that Node has a Node* called next that
// points to the next item in a list
// Returns the head of the reversed list if successful, else NULL / 0
Node *reverse( Node *head )
{
Node *prev = NULL;
while( head != NULL )
{
// Save next since we will destroy it
Node *next = head->next;
// next and previous are now reversed
head->next = prev;
// Advance through the list
prev = head;
head = next;
}
return previous;
}
I would have made a prepend function, and done the following:
struct node* prepend(struct node* root, int value)
{
struct node* new_root = malloc(sizeof(struct node));
new_root->next = root;
return new_root;
}
struct node* reverselist(struct node* inlist)
{
struct node* outlist = NULL;
while(inlist != NULL) {
struct node* new_root = prepend(outlist, inlist->value);
outlist = new_root;
inlist = inlist->next;
}
return outlist;
}
Have not tested this, but guess you grasp the idea of it. Might be just your variable names, which don't describe anything, but I think this approach is cleaner, and easier to understand what actually happens.
EDIT:
Got a question why I don't do it inplace, so I'll answer it here:
Can you do it inplace? Are you sure you don't wish to keep the
original list?
Do you need to do it inplace? Is the malloc to time consuming/is this a performance critical part of your code? Remember: premature optimization is the root of all evil.
Thing is, this is a first implementation. It should work, and not be optimized. It should also have a test written before this implementation is even thought of, and you should keep this slow, un-optimized implementation until the test passes, and you have proved that it's to slow for your use!
When you have a passing unit test, and proven the implementation to be to slow, you should optimize the code, and make sure it still passes the test, without changing the test.
Also, is it necessary inplace operations which is the answer? What about allocating the memory before reverting it, this way you only have one allocation call, and should hopefully get a nice performance boost.
This way everyone is happy, you have a cleaner code and avoid the risk of having Uncle Bob showing up at your door with a shotgun.