I am learning about linked lists and how to create them in C with structs and pointers. I have an example below. From my understanding the called push() passes the beginning memory location of our struct where the head node lies as an argument. The parameter of our push() function takes a struct node as a pointer to pointer so it is passed as a reference, not an actual copy. So the first pointer of our struct node** headref is just a pointer to the memory location of our head node and the second pointer points to the value, which is the next memory location that the head node points to. We create a new node called newnode inside our struct node by assigning it some memory. We then create an int type data inside this node.
Okay assuming everything I said is correct, the next part is what i am confused on.
newNode->next= *headRef;
This line from what i can understand dereferences the headref so this would just have the headref pointing to the head node. Then we have a pointer operation where what the headref is pointing to will then also be what our pointer next will be pointing to. Based on that the pointer next in our new node(newnode) will be pointing to the head pointer.
The next line which i am also confused on is:
*headRef = newNode;
What the dereferenced headref pointer is pointing to, which is the head node, will be pointing now to our newnode.
Based on that there should be a new node called newnode with an int data and a next pointer linking our newnode to the head. Then the headref pointer(or is it the head node?) will point to the new node. I know this isn't correct because the pointer next of our newnode should be pointing to the second node so our newnode can be linked in the struct. I also don't believe that i am understanding the pointer to pointer and dereferencing in the above two lines of code.
Code:
void Push(struct node** headRef, int data) {
struct node* newNode = malloc(sizeof(struct node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
void PushTest(void) {
struct node* head = BuildTwoThree(); // suppose this returns the list {2, 3}
Push(&head, 1);
Push(&head, 13);
// head is now the list {13, 1, 2, 3}
}
void Push(struct node** headRef, int data) {
headRef contains an address of address where struct node is located. It is an address of the head variable in PushTest function.
struct node* newNode = malloc(sizeof(struct node));
Here we created a newNode. It contains an address of memory where node struct is located.
newNode->data = data;
set the data of newNode to the value of the data parameter passed into the Push function - OK.
newNode->next = *headRef;
set the newNode->next to the address of the structure located in the head variable. For example, if we have written
void Push(struct node* head, int data) {
...
newNode->next = head;
Next, we need to change the head variable to the newNode. If head variable was passed by reference, we could simply write this, like in C++:
void Push(struct node* &head, int data) {
...
head = newNode;
But in plain C we have to pass the address where head variable is located, so we can write into that address the pointer to the structure newNode we created in the Push function:
*headRef = newNode;
is equivalent to the write the newNode pointer to the variable whose address is located inside the headRef variable
And there is a problem in your logic:
The parameter of our push() function takes a struct node as a pointer
to pointer so it is passed as a reference, not an actual copy.
Actually, we pass a copy of temporary variable that contains an address of node variable, not the reference. There is pass-by-reference method in C++, it uses ampersand to declare that the variable passed to function is a reference, not a copy of original variable.
You are actually both correct and not correct at the same time. The push function adds a new node at the head of the list. After this function is called the new node is the new head, and the previous head node is now the second (next) node in the list. So your observations are correct, but your conclusion is not.
I recommend you step through the code in a debugger, while watching all pointers and their contents, to see what happens. It might make things clearer for you.
Ok. I think I can explain.
head is the head pointer of linked list. headRef points to head. So *headRef is head pointer(pass by reference).
so newNode->next will now point to head of the struct ( which has value 2, thats why the nodes are getting inserted at front).
Now in this line *headRef = newNode;
*headRef is assigned the value of newNode so the head is now changed to newNode in the original structure.
Again when you pass &head, you are passing the head which contains value 1 now.
Let's copy this here, for simpler reference:
1 void Push(struct node** headRef, int data) {
2 struct node* newNode = malloc(sizeof(struct node));
3 newNode->data = data;
4 newNode->next = *headRef;
5 *headRef = newNode;
6 }
For the sake of simplicity, I numbered the lines.
Now, headRef itself is just a pointer to the variable that held the header of the list -- therefore, it contains no useful information by itself. By dereferencing that pointer, you're gaining access to the contents of the variable itself (head in PushTest()). Therefore, *headRef is essentially the way you get access to head. Now, since head is the top node, so is *headRef -- they hold the same value, since they are the same address. What line 4 does is, assign the value of *headRef (i.e., the value of head) to the new node's next link. In the same fashion, line 5 assigns the new node to *headRef, which is the same as assigning it to head, since headRef is a pointer to head -- meaning that *headRef is the value of head.
The key point here is, headRef is a reference (a pointer) to head, so *headRef and head are equivalents (scope rules aside).
Related
I'm trying to create a singly linked list with nodes containing two parameters. Whenever I enqueue another node using the tail pointer, the head pointer takes the same value as the new node.
I'm sure the pointers are pointing to the same memory location or something similar, but I'm not sure how to fix this.
struct node
{
struct process *p;
struct node *next;
}
struct node* head;
struct node* tail;
void enqueue(struct process* newProcess)
{
struct node *newNode = malloc(sizeof(struct node));
newNode->p = malloc(sizeof(struct process));
newNode->p = newProcess);
if(tail==NULL)
{
head = tail = newNode;
return;
}
tail = tail->next;
tail = newNode;
}
I'd like to use this function to be able to create a singly linked list with the head node pointing to the first element in the list and the tail node pointing to the last element in the list. The current code results in both variables representing the last element added.
Setting tail = tail->next is setting tail to null because it isn't set the first time around, and then both tail and head are immediately overwritten in the subsequent call.
There are some issues here. First, to fix your problem, replace the two last lines with:
tail = tail->next = newNode;
Also, consider this:
tail = tail->next;
tail = newNode;
What is the point of assigning a variable to a value if you reassign the same variable in the next statement? You have the same error earlier on too:
newNode->p = malloc(sizeof(struct process));
newNode->p = newProcess;
Because of the second line, the only thing you achieve with the first line is a memory leak. Remove the first line completely.
I am trying to understand how the code below for creating a singly linked list works using a double pointer.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
void push(struct Node** headRef, int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
//Function to implement linked list from a given set of keys using local references
struct Node* constructList(int keys[], int n) {
struct Node *head = NULL;
struct Node **lastPtrRef = &head;
int i, j;
for(i = 0; i < n; i++) {
push(lastPtrRef, keys[i]);
lastPtrRef = &((*lastPtrRef)->next); //this line
if((*lastPtrRef) == NULL) {
printf("YES\n");
}
}
return head;
}
int main() {
int keys[] = {1, 2, 3, 4};
int n = sizeof(keys)/sizeof(keys[0]);
//points to the head node of the linked list
struct Node* head = NULL;
head = constructList(keys, n); //construct the linked list
struct Node *temp = head;
while(temp != NULL) { //print the linked list
printf(" %d -> ", temp->data);
temp = temp->next;
}
}
I understand the purpose of using the double pointer in the function push(), it allows you to change what the pointer headRef is pointing to inside the function. However in the function constructList(), I don't understand how the following line works:
lastPtrRef = &((*lastPtrRef)->next);
Initially lastPtrRef would be pointing to head which points to NULL. In the first call to push(), within the for loop in constructList(), the value that head points to is changed (it points to the new node containing the value 1). So after the first call to push(), lastPtrRef will be pointing to head which points to a node with the value of 1. However, afterwards the following line is executed:
lastPtrRef = &((*lastPtrRef)->next);
Whereby lastPtrRef is given the address of whatever is pointed to by the next member of the newly added node. In this case, head->next is NULL.
I am not really sure what the purpose of changing lastPtrRef after the call to push(). If you want to build a linked list, don't you want lastPtrRef to have the address of the pointer which points to the node containing 1, since you want to push the next node (which will containing 2) onto the head of the list (which is 1)?
In the second call to push() in the for loop in constructList, we're passing in lastPtrRef which points to head->next (NULL) and the value 2. In push() the new node is created, containing the value 2, and newNode->next points to head->next which is NULL. headRef in push gets changed so that it points to newNode (which contains 2).
Maybe I'm understanding the code wrong, but it seems that by changing what lastPtrRef points to, the node containing 1 is getting disregarded. I don't see how the linked list is created if we change the address lastPtrRef holds.
I would really appreciate any insights as to how this code works. Thank you.
This uses a technique called forward-chaining, and I believe you already understand that (using a pointer-to-pointer to forward-chain a linked list construction).
This implementation is made confusing by the simple fact that the push function seems like it would be designed to stuff items on the head of a list, but in this example, it's stuffing them on the tail. So how does it do it?
The part that is important to understand is this seemingly trivial little statement in push:
newNode->next = *headRef
That may not seem important, but I assure you it is. The function push, in this case, does grave injustice to what this function really does. In reality it is more of a generic insert. Some fact about that function
It accepts a pointer-to-pointer headRef as an argument, as well as some data to put in to the linked list being managed.
After allocating a new node and saving the data within, it sets the new node's next pointer to whatever value is currently stored in the dereferenced headRef pointer-to-pointer (so.. a pointer) That's what the line I mentioned above accomplishes.
It then stores the new node's address at the same place it just pulled the prior address from; i.e. *headRef
Interestingly, it has no return value (it is void) further making this somewhat confusing. Turns out it doesn't need one.
Upon returning to the caller, at first nothing may seem to have changed. lastPtrRef still points to some pointer (in fact the same pointer as before; it must, since it was passed by value to the function). But now that pointer points to the new node just allocated. Further, that new node's next pointer points to whatever was in *lastPtrRef before the function call (i.e. whatever value was in the pointer pointed to by lastPtrRef before the function call).
That's important. That is what that line of code enforces, That means if you invoke this with lastPtrRef addressing a pointer pointing to NULL (such as head on initial loop entry), that pointer will receive the new node, and the new node's next pointer will be NULL. If you then change the address in lastPtrRef to point to the next pointer of the last-inserted node (which points to NULL; we just covered that), and repeat the process, it will hang another node there, setting that node's next pointer to NULL, etc. With each iteration, lastPtrRef addresses the last-node's next pointer, which is always NULL.
That's how push is being used to construct a forward linked list. One final thought. What would you get for a linked list if you had this:
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node* next;
};
void push(struct Node** headRef, int data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = *headRef;
*headRef = newNode;
}
int main()
{
//points to the head node of the linked list
struct Node* head = NULL;
push(&head, 1);
push(&head->next, 2);
push(&head->next, 3);
for (struct Node const *p = head; p; p = p->next)
printf("%p ==> %d\n", p, p->data);
}
This seemingly innocent example amplifies why I said push is more of a generic insert than anything else. This just populates the initial head node.
push(&head, 1);
Then this appends to that node by using the address of the new node's next pointer as the first argument, similar to what your constructList is doing, but without the lastPtrRef variable (we don't need it here):
push(&head->next, 2);
But then this:
push(&head->next, 3);
Hmmm. Same pointer address as the prior call, so what will it do? Hint: remember what that newNode->next = *headRef line does (I droned on about it forever; I hope something stuck).
The output of the program above is this (obviously the actual address values will be different, dependent to your instance and implementation):
0x100705950 ==> 1
0x10073da90 ==> 3
0x100740b90 ==> 2
Hope that helps.
Below is the code for creation of linked list using local reference logic.
Not able to understand the code inside the for loop especially the 2nd line. (see // HERE)
Can somebody please elaborate how this logic is working.
void push(struct Node** head_ref, int new_data)
{
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = new_data;
newNode->next = *head_ref;
*head_ref = newNode;
return;
}
struct Node* buildWithLocalRef()
{
int i=0;
struct Node *head = NULL;
struct Node **lastptrRef = &head;
for(i=1;i<6;i++)
{
push(lastptrRef,i);
lastptrRef = &((*lastptrRef)->next); // HERE
}
return head;
}
int main()
{
struct Node* head;
head = buildWithLocalRef();
printList(head);
return 0;
}
The technique you're seeing is building a linked list by forward-chaining. It is the most direct, and sensible way to build an ordered list from beginning to end, where the list does not have a tail pointer (and yours does not).
There are no "references" here. This isn't C++. This is using a pointer to pointer. The variable name is dreadfully named, btw. How it works is this:
Initially the list is empty, head is NULL
A pointer to pointer, lastptrRef will always hold the address of (not the address in; there is a difference) the next pointer to populate with a new dynamic node allocation. Initially that pointer-to-pointer holds the address of the head pointer, which is initially NULL (makes sense, that is where you would want the first node hung).
As you iterate the loop a new node is allocated in push . That node's next pointer is set to whatever value is in the pointer pointed to by lastptrRef (passed as head_ref in the function), then the pointer pointed to by lastptrRef is updated to the new node value.
Finally, lastptrRef is given the address of the next member in the node just added, and the process repeats.
In each case, lastptrRef hold the address of a pointer containing NULL on entry into push. This push function makes this harder to understand. (more on that later). Forward chaining is much easier to understand when done directly, and in this case, it would make it much, much easier to understand
struct Node* buildWithLocalRef()
{
struct Node *head = NULL;
struct Node **pp = &head;
for (int i = 1; i < 6; i++)
{
*pp = malloc(sizeof **pp);
(*pp)->data = i;
pp = &(*pp)->next;
}
*pp = NULL;
return head;
}
Here, pp always holds the address of the next pointer we'll populate with a new node allocation. Initially, it holds the address of head. As each node is inserted pp is set to the address of the next pointer within the latest node inserted, thereby giving you the ability to continue the chain on the next iteration. When the loop is done, pp holds the address of the next pointer in the last node in the list (or the address of head of nothing was inserted; consider what happens if we just pull the loop out entirely). We want that to be NULL to terminate the list, so the final *pp = NULL; is performed.
The code you posted does the same thing, but in a more convoluted manner because push was designed to push items into the front of a list (apparently). The function always sets the pointer pointed to by head_ref to the new node added, and the node's next is always set to the old value in *head_ref first. Therefor, one can build a stack by doing this:
struct Node* buildStack()
{
struct Node *head = NULL;
for (int i = 1; i < 6; i++)
push(&head, i);
return head;
}
Now if you print the resulting linked list, the number will be in reverse order of input. Indeed, push lives up to its name here. Dual-purposing it to build a forward-chained list is creative, I'll grant that, but in the end it makes it somewhat confusing.
This appeared on one of the old exams on algorithms and data structure. It seems pretty simple but I need some help with understanding why it works.
The goal is to delete certain atoms from a singly linked list using only a pointer to head.
The structure of atom is:
struct at {
int element;
struct at *next;
};
typedef struct at atom;
The solution is:
void delete(atom **head)
{
while((*head)){
if((*head)->element%2){ /*just a condition for deleting*/
(*head)=(*head)->next; /*deleting the atom*/
} else {
head= &(*head)->next;
}
}
}
What I understand is that, in this function, "*head" is the actual head (pointer to the first atom) and "head" is a pointer to the actual head. Clearly, since I'll actually change the head and contents of the list, I need to pass a pointer to head.
I just can't understand how head= &(*head)->next seems to work. I've tried putting it down on paper and still can't make any sense of it. How does it not change anything and just jump to the next atom?
As you said *head is the actual head (pointer to the first atom) and head is a pointer to the actual head.In statement head= &(*head)->next;
we are updating head (which contains address of the actual head) with the address of address of node next to actual head node.
Now head is a pointer which store the address of head to next node not head node i.e next atom.
Consider a list
1->2->3->4->5
in this case initially head contains the address node 1, and *head is node 1 itself.
Now when we say head= &(*head)->next; it means head will store address of node 2.If we will do *head it return node 2.
struct node
{
int data;
struct node *next;
};
void addstart (struct node **n, int new_data){
struct node *new = (struct node*)malloc(sizeof(struct node));
new->data=new_data;
new->next=*n;
*n= new;
}
int main(){
struct node* head = NULL;
addstart(&head,5);
return 0;
}
i just want to know if head_node is also a new_node and both are same data type struct node then why we are defining it simply as
struct node* head = NULL;
and other nodes as:
struct node *new = (struct node*)malloc(sizeof(struct node));
struct node* head = NULL;
Because head is there to simply keeping track of the head of the list nothing else. Now don't the new node does the same?
yes it does. it keeps track of the memory that you allocate using malloc and then you point it out with subsequent node's next attribute.
head is of same type as newnode and both are same functonally. but we put a different meaning to head in that - we are making it point to the head of th list rather than making it a local variable which we can throw away.
Then also why head = NULL?
Ah! that's because we always create a node and make it's next attribute to point to the current head and then we change head to make the new node part of our linked list.
Initially the first node should be
+----+
| +------+
+----+ |
V
NULL (Well to make this NULL we just make head = NULL initially)
i want to know why we are not using malloc in head assignment case..
Because it's not needed - as simple as that. Why should we allocate an extra memory space and then assign it to head when the purpose of head is to just point to already allocated node. And when we add another extra node we add it to the head of the linked list and make it head. it's nothing other than pointing to the beginning of the list. That's it.
new is not a good variable name. Try some good one. Think a bit before naming a variable it will save you a lot of time in future.
Because when you declare head it doesn't have anything to point to yet, it indicates the list is empty , as soon as data is added it can point to the first node in the list.