I'm implementing a circularly linked list and am attempting to make the functions that will free the linked list and the nodes in the linked list. I have a LL_t
type that points to the head and tail of the linked list. And then a linked list of LL_node_t
nodes.
I have done my best to get the important parts of the code and the output that I am working with to better show my question.
My question is, why in my temp_llnode_ptr
start with this value temp_llnode_ptr: 0x7fffcf9c7310
, but after calling my free function end up with NEW temp_llnode_ptr: 0x7fffcf9c6010
. It starts with the address of my second node. I attempt to free my first node. Then after the function my temp_llnode_ptr
has a different value.
void
free_LL (LL_t** list)
{
LL_node_t** temp_llnode_ptr = NULL;
for (int i = 0; i < (*list)->LL_length; i )
{
printf("Inside FOR loop\n");
printf("list->head: %p\n", (void*)(*list)->head);
printf("list->tail: %p\n", (void*)(*list)->tail);
temp_llnode_ptr = &(*list)->head->next;
printf("temp_llnode_ptr: %p\n", (void*)(*temp_llnode_ptr));
printf("(*temp_llnode_ptr)->next: %p\n", (void*)(*temp_llnode_ptr)->next);
free_LL_node(&(*list)->head);
printf("NEW temp_llnode_ptr: %p\n", (void*)(*temp_llnode_ptr));
printf("NEW list->head: %p\n", (void*)(*list)->head);
}
}
void
free_LL_node (LL_node_t** node)
{
(*node)->next = NULL;
(*node)->data = NULL;
printf("node about to be FREED is: %p\n", (void*)(*node));
free(*node);
*node = NULL;
}
OUTPUT FROM PRINT FUNC (The nodes in my linked list)
Node#: 0 | Current node: 0x7fffcf9c72f0 | Node data: 10 | Next node 0x7fffcf9c7310
Node#: 1 | Current node: 0x7fffcf9c7310 | Node data: 20 | Next node 0x7fffcf9c7330
Node#: 2 | Current node: 0x7fffcf9c7330 | Node data: 30 | Next node 0x7fffcf9c72f0
Inside FOR loop
list->head: 0x7fffcf9c72f0
list->tail: 0x7fffcf9c7330
temp_llnode_ptr: 0x7fffcf9c7310 <--- Why do these change?
(*temp_llnode_ptr)->next: 0x7fffcf9c7330
node about to be FREED is: 0x7fffcf9c72f0
NEW temp_llnode_ptr: 0x7fffcf9c6010 <--- Why do these change?
NEW list->head: (nil)
CodePudding user response:
Let X
be the thing that (*list)->head
points to. Then temp_llnode_ptr
is set to the address of X->next
. Calling free_LL_node(&(*list)->head);
frees X
. Once X
is freed, the contents of its memory are no longer reliable. Among other things, they may well have been changed by the memory management routines, using that memory for its own purposes. X->next
is in that memory, so its contents may have changed. Printing (*temp_llnode_ptr)
attempts to print the contents of X->next
.
Since temp_llnode_ptr
points to an object which has been freed, its value is indeterminate, per the C standard. (This is correct, as specified by the C standard, after free(p)
, the value of p
is no longer determined. Even though free
cannot change the memory used to hold p
, its “value” in a semantic sense may be tied to other data in memory, and that data may be altered by p
, so that its “value” is no longer determined.) Additionally, *temp_llnode_ptr
attempts to use that memory, and the behavior of that is not defined by the C standard.
CodePudding user response:
Let's look at the problematic part:
temp_llnode_ptr = &(*list)->head->next;
printf("temp_llnode_ptr: %p\n", (void*)(*temp_llnode_ptr));
free_LL_node(&(*list)->head);
printf("NEW temp_llnode_ptr: %p\n", (void*)(*temp_llnode_ptr));
The first line sets temp_llnode_ptr
to point to the next
variable in the first node.
The second line looks at the value in that variable (the value which is the address of the second node) and prints it.
The third line frees the first node.
The second line looks at the value in that variable - the variable which is part of the first node - which we just freed - and prints it. Because we just freed this variable we're not supposed to use it any more. In this case it looks like the memory management system has used that spot in memory to hold some pointer related to memory management. That doesn't matter though - that's not your problem - your problem is that you're not supposed to use it at all after you free it.
You seem to think that temp_llnode_ptr
points to the next node. I think that works for your purpose, so you can do that. temp_llnode_ptr
can be just a pointer (instead of a pointer-to-a-pointer a.k.a. double pointer) and then you have a bunch of extra &
s and *
s to delete as well.
If you do temp_llnode_ptr = (*list)->head->next;
(delete an &
) then temp_llnode_ptr will point to the next node (instead of pointing to next
which points to the next node) and you should be able to work out the rest yourself.