How to modify head pointer in list  - Page 1
Forum section: electronics / general

author: Dadu@
author_rank: Contributor
timestamp: March 08, 2022, 06:35:30 am
content: I have a two pointer head and new. I have created one node in functionCode:#include <stdio.h>#include <stdlib.h>struct node{int data;struct node* next;};void ADD ( struct node *head, int value){struct node * new = malloc(sizeof(*new));if(new != NULL ){new -> data = value;new -> next = NULL;}}int main (){struct node * head = NULL;ADD(&head,4);return 0;}new pointer point to current node in function. I want head pointer to point current node.How the head ponter will change in function ?

author: magic
author_rank: Super Contributor
timestamp: March 08, 2022, 06:51:30 am
content: Pass a pointer to the head variable to add() - make its type node**.Code:void add(struct node **head, int value) {...new->data = value;new->next = *head;*head = new;}Surprisingly, your main is already right, which means that the code that you posted wouldn't compile.

author: Dadu@
author_rank: Contributor
timestamp: March 08, 2022, 07:07:28 am
content: Quote from: magic on March 08, 2022, 06:51:30 amWhat does this line means ?Code:new->next = *head;What happen in this situation ?Code:new->next = head;

author: magic
author_rank: Super Contributor
timestamp: March 08, 2022, 07:27:31 am
content: In add(), head is a pointer to a pointer to a node. So *head is simply the pointer to a node pointed to by head.In this example, head is a pointer to the head variable in main, so *head is the head variable in main.Quote from: Dadu@ on March 08, 2022, 07:07:28 amNothing useful, try it

author: rstofer
author_rank: Super Contributor
timestamp: March 08, 2022, 03:20:13 pm
content: Quote from: Dadu@ on March 08, 2022, 06:35:30 amIt seems to me that you are only allocating a pointer to the struct, not an instance of the struct itself. Check how the sample code allocates a new item.Here is some sample codehttps://www.tutorialspoint.com/data_structures_algorithms/linked_list_program_in_c.htmThis stuff is all over the Internet and some of it might actually work.

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 08, 2022, 04:56:42 pm
content: Quote from: rstofer on March 08, 2022, 03:20:13 pmNo,malloc(sizeof *new)allocates enough memory for the struct pointed to bynew.If it weremalloc(sizeof new)then it would only allocate enough for the pointer.

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 08, 2022, 05:12:56 pm
content: Consider the following example program:Code:#include <stdlib.h>#include <stdio.h>struct node{struct node *next;int data;};struct node *node_new(int data){struct node *n;n = malloc(sizeof *n);if (!n) {fprintf(stderr, "node_new(): Out of memory.\n");exit(EXIT_FAILURE);}n->next = NULL;n->data = data;return n;}void node_prepend(struct node **list, int data){struct node *n;if (!list) {fprintf(stderr, "node_prepend(): NULL list pointer!\n");exit(EXIT_FAILURE);}n = node_new(data);n->next = *list;*list = n;}void node_append(struct node **list, int data){struct node *n;if (!list) {fprintf(stderr, "node_append(): NULL list pointer!\n");exit(EXIT_FAILURE);}n = node_new(data);if (*list) {struct node *last = *list;while (last->next)last = last->next;last->next = n;} else {/* List is empty. */*list = n;}}void node_printall(struct node *all, FILE *out){if (out) {const char *sep = "";while (all) {fprintf(out, "%s%d", sep, all->data);all = all->next;sep = ", ";}fprintf(out, "\n");fflush(out);}}void node_freeall(struct node **list){if (list) {struct node *next = *list;*list = NULL;while (next) {struct node *n = next;next = next->next;n->next = NULL;n->data = 0;free(n);}}}int main(void){struct node *mylist = NULL;node_append(&mylist, 1);node_append(&mylist, 2);node_prepend(&mylist, 3);node_append(&mylist, 4);node_prepend(&mylist, 5);node_prepend(&mylist, 6);node_printall(mylist, stdout);node_freeall(&mylist);mylist = NULL;return EXIT_SUCCESS;}Now, compare thenode_prepend()andnode_append()functions. The former prepends the new data to the list, whereas the latter appends the data to the list. Because both functions may modify the pointer to the first element in the list, they need to be passed a pointer to the pointer to the first element in the list.Thenode_freeall()function also takes the pointer to the pointer to the first element in the list, because it frees each element in the list.Because thenode_printall()function only traverses the list without modifying anything, it takes a pointer to the first element in the list as a parameter.

author: Dadu@
author_rank: Contributor
timestamp: March 09, 2022, 03:55:20 am
content: If I am not mistaken this line de-referenced pointer head assign its valueCode:new->next = *head;here is head pointer modified by assigning value of newCode:*head = new;

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 09, 2022, 06:03:53 pm
content: Quote from: Dadu@ on March 09, 2022, 03:55:20 amYes, in a function that takes a parameterstruct node **head, which is a pointer to the pointer to the first element in the list.Then,headis a pointer to the pointer to the first element in the list,and*headis the pointer to the first element in the list.Within the function, modifyingheadis not visible to the caller (because in C, we pass variables by value). But, modifying*headis. The idea, of course, is that the caller will pass a pointer to the variable it uses to point to the first element in the list, i.e.&mylistforhead.Put another way, if we have created nodenewto be inserted as the new first element in the list, we doCode:/* Hang off the original list from the new element, */new->next = *head;/* and update the pointer to the first element to point to the new element. */*head = new;When appending to the end of the list, we have two cases: If the list is empty,*head == NULL, and we set the pointer to the first element to point to the new element,*head = new;. Otherwise, the list is not empty, and it must have a last element; the one that has->next == NULL. When we find that, we set that->next = new.

author: Dadu@
author_rank: Contributor
timestamp: March 14, 2022, 10:27:44 am
content: Here is my codeCode:#include <stdio.h>#include <stdlib.h>struct node{int data;struct node* next;};void ADD ( struct node **head, int value){struct node * new = malloc(sizeof(*new));if(new != NULL ){new->data = value;new->next = *head;*head = new;}}int main (){struct node * head = NULL;ADD(&head, 1);printf(" Node 1 Data = %d \n", head -> data);printf(" Node 1 Data = %p \n", &head -> data);printf(" Node 1 next = %p \n", head -> next);ADD(&head, 2);printf(" Node 2 Data = %d \n", head -> data);printf(" Node 2 Data = %p \n", &head -> data);printf(" Node 2 next = %p \n", head -> next);ADD(&head, 3);printf(" Node 3 Data = %d \n", head -> data);printf(" Node 3 Data = %p \n", &head -> data);printf(" Node 3 next = %p \n", head -> next);return 0;}Node 1 Data = 1Node 1 Data = 00DB0D18Node 1 next = 00000000Node 2 Data = 2Node 2 Data = 00DB0D48Node 2 next = 00DB0D18Node 3 Data = 3Node 3 Data = 00DB0D58Node 3 next = 00DB0D48I see that pointer of current node point to the previous node but I want it to point the address of next nodeHow to point to address of next node in list

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 14, 2022, 06:20:07 pm
content: Of course all this is pretty "textbook" stuff. I haven't implemented dynamic lists in that way in ages. While this is certainly correct and flexible, this kind of implementation is - in general - pretty inefficient cache-wise.Interestingly, I think the Linux kernel source code is full of those linked lists. But it mostly uses custom allocators AFAIK, so the cache issues can be mitigated, while if you use general-purpose allocators such as malloc(), things can get nasty pretty fast.

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 16, 2022, 04:34:55 am
content: Consider a functionpop()that detaches and returns a pointer to the first element in the list:Code:struct node *pop(struct node **list){struct node *first;/* Safety check, will not trigger in normal code */if (list == NULL)return NULL;/* If the list is empty, return NULL. */if (*list == NULL)return NULL;/* Detach the first element in the list: */first = *list; /* Keep track of the original first element, */*list = first->next; /* advance the list to start at the second element, */first->next = NULL; /* and isolate the originally first element from the list. *//* Return the now detached, originally first element in the list. */return first;}The caller does need to free the popped element. If you just wish to discard the first element in the list, you can safely always dofree(pop(&mylist));, becausefree(NULL);does nothing (and is safe to do).The above is exactly equivalent toCode:struct node *pop(struct node **list){struct node *first;if (!list)return NULL;if (!*list)return NULL;first = *list;*list = (*list)->next;first->next = NULL;return first;}in case you wonder.Please also note how important descriptive comments are to the maintenance and understanding of the code.Because I myself did not learn this when I learned to program on my own, I am still struggling with this. Do not repeat my mistake!Learn to write comments that describe your intent as a programmer; to describe what you intend the code to do.Writing comments that describewhatthe code does –– like "increment x by 5" –– are less than useless, because as programmers, we can read the code itself, we do not need to read that twice. (This means that the second comment,"If the list is empty, return NULL", is borderline; if I had written it as"If *list is NULL, return NULL", it would definitely be useless and annoying comment. Consider the difference between those two wordings, to see what I mean.)It is the higher, more concept-level things, that the comments should describe.Quote from: SiliconWizard on March 14, 2022, 06:20:07 pmYes, but this is a necessary step togrokthis stuff before one can advance to the cache-efficient and dedicated dynamic allocator stuff.That is, I personally consider this thread as an example of how one would go about learning this stuff in C down to the "intuitive" level (where one uses these structures, even if they have their downsides, without much cognitive effort or worries), before advancing to the more interesting, more complicated dynamic memory stuff: step by step, one step at a time.You certainly wouldn't want to tell someone who wasn't yet completely familiar with this kind of pointery stuff in C to implement and use their own specialized dynamic memory allocators, would you?(Although, I must admit a good book on data structures in C –– see old threads in this sub-forum! –– would probably benefit OP more than this thread.)Some of the actually real-world useful data structures, likebinary heaps, specifically min- and max-heaps implemented as an array, do not necessarily use pointers at all internally; but to makeuseof them, you end up needing togrokhow pointers work in C anyway. (That is, do not expect that to be "easier" than any pointer stuff: itlooksdeceptively simple, but the rules of how to implement it a way that works reliably with other code, is not simple. There are lots and lots of implementation choices, but only a small subset of them work together well as a whole!)My favourite example of this is a min-heap of event timestamps, to implement timeouts or timed events. Each element in the binary heap contains only a timestamp of the event, and a pointer or index to the event slot in an array or list of events. Each event slot contains a pointer or index back to the element; the two are linked bidirectionally to each other. As the heap is a min-heap, the root always contains the next event to occur. When it occurs, it is removed from the heap, and the event slot updated to indicate the event has elapsed. When the heap is modified, elements are percolated towards the root, and their links in the event slot updated to match.I use that in real-world code, especially in systems programming (daemons and services written in C in a full blown OS like Linux or Windows), with a dedicated thread handling the timeout stuff. It runs only very rarely, most of the time waiting for the next event or an event cancelation/addition, and does not use much resources (CPU or memory) to do what it does; so it is a very, very efficient way to do it. On POSIXy systems, it can even interrupt blocking I/O calls in specific threads (delivering an internal POSIX signal).However, the way the event in the heap and in the heap slot are two-way linked to each other, does mean one has to be very, very careful to make sure they remain consistent. The fact that they also need to be thread-safe, makes for a lot of options in locking options, some of them "interesting". (I do not actually know of a way to do this locklessly in a multithreaded environment). That "interesting" means that while some of the options look perfectly fine in a separate exercise or unit test, in real life use they cause a cascade of increasingly undesirable neighboring code. The simplest one, a single mutually exclusive lock (mutex) governing access to both the heap and the event slot list/array, works surprisingly well; anything more fine-grained needs a lot of applied experience to actually turn out better in real world work loads.(Why binary min-heap for this? As Mark Allen Weiss shows inAlgorithm Analysis and Data Structures in C, for uniformly random keys (timestamps), the number of percolation events (moving a heap entry towards or outwards from the root, level by level) on average is approximatelye≃ 2.718 per key addition or deletion, with an absolute maximum of log2Nevents forNexisting entries in the heap. This makes it a very efficient choice. It also shows how importantalgorithm analysisis when choosing algorithms. Yet, unless you have the necessary skills to implement those algorithms in C, the analysis part is useless to you. The two –– implementing and analysis ––, should in my opinion, be developed almost in tandem, starting on the more mathematical analysis part as soon as one can implement all the basic abstract data structures in C.)

author: DiTBho
author_rank: Super Contributor
timestamp: March 16, 2022, 10:38:44 am
content: Quote from: Nominal Animal on March 16, 2022, 04:34:55 amSluuurp, I have a copy of this book in my shelf!Algorithms in C, Parts 1-4+5 in Bundle: Fundamentals, Data Structures, Sorting, Searching, and Graph Algorithms by Robert Sedgewick, HardcoverIntroduction to Algorithms, 3rd Edition MIT Press, by Thomas H. Cormen, HardcoverBoth hardcover premium version, perhaps a bit snob, but these books are worth to have, and they are worth the books face beautiful view of themselves on the shelf

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 16, 2022, 05:55:52 pm
content: Quote from: Nominal Animal on March 16, 2022, 04:34:55 amOf course! That's why I said "textbook". It's definitely needed as a learning step.Just mentioned this, because I've actually seen many "experienced" developers still using this kind of structure mindlessly, not being aware of its shortcomings. And unfortunately, more "advanced" approaches that are more efficient/cache-friendly/... are often *not* taught at universities (except maybe in the most advanced courses), so unless one takes advanced CS courses, or one is otherwise interested in the topic and constantly learning (which should be the general case for engineers, but far from being that common), that may elude many people entirely.For a short introduction to the matter, less experienced people can watch this:(the OP can watch it of course, but not before having mastered the concept of linked lists first!)

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 20, 2022, 07:02:36 am
content: A side track:I recalled that when I first started learning about pointers in C, I wondered why the "handle" to the list was the same type as the pointers to individual list elements. That does not have to be the case, it just is the simplest case. Here is an example of a case where the handle does differ.If one wants to both prepend and append to a singly-linked list efficiently, it makes sense to maintain a pointer to both the first and the last elements in the list. For example,Code:#include <stdlib.h>#include <stdio.h>struct list_item {struct list_item *next;int data;};struct list {struct list_item *head; /* Or "first" */struct list_item *tail; /* Or "last" */};#define LIST_INIT { NULL, NULL }To declare a list variable, it is useful to initialize it to NULL pointers, which is what theLIST_INITmacro intends to help with:Code:struct list my_list = LIST_INIT; /* my_list is now an empty list. */The common helper functions, for initializing a list (in case you want to dostruct list my_list; list_init(&my_list);instead of the above, for any reason), allocating a new list item, and freeing an entire list, are for exampleCode:static void list_init(struct list *handle){/* Safety check; could also be an assert() */if (!handle) {fprintf(stderr, "list_init(): NULL handle!\n");exit(EXIT_FAILURE);}handle->head = NULL;handle->tail = NULL;}static struct list_item *list_item_new(int data){struct list_item *item;item = malloc(sizeof *item);if (!item) {fprintf(stderr, "list_item_new(): Out of memory.\n");exit(EXIT_FAILURE);}item->next = NULL;item->data = data;return item;}static void list_free(struct list *handle){struct list_item *curr, *next;if (!handle)return;next = handle->head;handle->head = NULL;handle->tail = NULL;while (next) {curr = next;next = next->next;/* 'Poison' list item to help detect use-after-free bugs. */curr->next = NULL;curr->data = 0;free(curr);}}In case you are wondering, I am naming the types and functions using patternlibrary_type_action, withlistas the "library" name,listandlist_itemas the "type" names, andinit,new, andfreeas "actions". Because C does not have namespaces (any way specify which library or header file a function name or type refers to), some variant of this kind of naming style is used to keep library-like function and type names from colliding with others.Because the list is singly linked, it is very efficient to insert a new element at the beginning of the list, and to remove the element from the beginning of the list. Because we also have a pointer to the last element in the list, we can also insert an element to the end of the list. (To remove the element at the end of the list, however, we do need to traverse the entire list to find the element just before the last element, so that will stay inefficient/slow.)Code:static void list_push(struct list *handle, struct list_item *item){/* Safety checks; could also be assert()s */if (!handle) {fprintf(stderr, "list_prepend(): NULL handle!\n");exit(EXIT_FAILURE);}if (!item) {fprintf(stderr, "list_prepend(): NULL list item!\n");exit(EXIT_FAILURE);}if (!(handle->head)) {item->next = NULL;handle->head = item;handle->tail = item;} else {item->next = handle->head;handle->head = item;}}static struct list_item *list_pop(struct list *handle){struct list_item *item;/* Safety check; could also be an assert() */if (!handle) {fprintf(stderr, "list_init(): NULL handle!\n");exit(EXIT_FAILURE);}item = handle->head;if (item) {handle->head = item->next;if (!handle->head)handle->tail = NULL;item->next = NULL;}return item;}static void list_append(struct list *handle, struct list_item *item){/* Safety checks; could also be assert()s */if (!handle) {fprintf(stderr, "list_prepend(): NULL handle!\n");exit(EXIT_FAILURE);}if (!item) {fprintf(stderr, "list_prepend(): NULL list item!\n");exit(EXIT_FAILURE);}if (!(handle->head)) {item->next = NULL;handle->head = item;handle->tail = item;} else {item->next = NULL;handle->tail->next = item;handle->tail = item;}}static struct list_item *list_trim(struct list *handle){struct list_item *item, *prev;/* Safety check; could also be an assert() */if (!handle) {fprintf(stderr, "list_init(): NULL handle!\n");exit(EXIT_FAILURE);}item = handle->head;/* List empty? */if (!item)return NULL;prev = NULL;/* Find the last item in the list, with prev being the previous item. */while (item->next) {prev = item;item = item->next;}/* Only one item in the list? */if (!prev) {handle->head = NULL;handle->tail = NULL;return item;}/* Detach the item from the list. */handle->tail = prev;prev->next = NULL;return item;}If we tie this together in an example program which again can print out the list structure in Graphviz DOT format,Code:static void list_dot(struct list *handle, FILE *out, const char *handle_name){struct list_item *curr, *prev;/* Safety checks; could also be assert()s */if (!handle) {fprintf(stderr, "list_dot(): NULL list handle!\n");exit(EXIT_FAILURE);}if (!handle_name) {fprintf(stderr, "list_dot(): NULL list handle name!\n");exit(EXIT_FAILURE);}/* In order to make output optional easily in an application,we allow out==NULL to indicate no output is wanted. */if (!out)return;fprintf(out, "digraph {\n");fprintf(out, " \"0\" [ shape=\"rect\", label=\"%s\" ];\n", handle_name);/* Describe each list item first, then the edge to that list item. */prev = NULL;curr = handle->head;while (curr) {fprintf(out, " \"%p\" [ shape=\"oval\", label=\"%d\" ];\n", (void *)curr, curr->data);if (prev)fprintf(out, " \"%p\" -> \"%p\";\n", (void *)prev, (void *)curr);prev = curr;curr = curr->next;}/* Describe the head and tail edges last. */fprintf(out, " \"0\" -> \"%p\" [ taillabel=\"head\" ];\n", (void *)(handle->head));fprintf(out, " \"0\" -> \"%p\" [ taillabel=\"tail\" ];\n", (void *)(handle->tail));fprintf(out, "}\n");fflush(out);}int main(void){struct list my_list = LIST_INIT;list_push(&my_list, list_item_new(1));list_push(&my_list, list_item_new(2));list_push(&my_list, list_item_new(3));list_dot(&my_list, stdout, "my_list");list_free(&my_list);return EXIT_SUCCESS;}the output is something like[ my_list ]head tail│ │3 │╲ │2 │╲│1 │with all lines actually being arrows pointing down, if you usedotto display the output. As I use Linux, I usegcc -Wall -Wextra -Wno-unused-function -O2 example.c -o ex && ./ex | dot -Tx11to compile, run, and display the graph in one commmand.If we append alist_append(&my_list, list_item_new(4));in main(), just beforelist_dot(&my_list, stdout);line, we get[ my_list ]head tail│ │3 │╲ │2 │╲ │1 │╲│4and so on.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 20, 2022, 06:59:10 pm
content: Quote from: Nominal Animal on March 20, 2022, 07:02:36 amIt doesn't have to, of course. But it has benefits (and pitfalls): any of the list element is itself a list. The recursive nature of this structure lends itself well to recursive algorithms and formal proofing.You can absolutely do things differently. For efficiency reasons (except in maybe very particular cases), I often implement lists and trees with "arrays".Then, unless the list is purely sequential or the tree is a pure binary tree, the "handle" contains an index to the root element, and each element indices to the children if needed. That can be a very effective structure cache-wise.

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 21, 2022, 01:13:28 am
content: Quote from: SiliconWizard on March 20, 2022, 06:59:10 pmYep. In molecular dynamics simulations, when a separate list of neighboring particles is maintained, it is usually one or moreVerlet lists. It is particularly useful with force field models (that chemists use to model proteins and such), where you have not only pairwise interactions, but also each unique triplet and quartet too; you reiterate over the same sublists quite a lot. The array form helps a lot with cache behaviour, due to data locality.One of the more interesting data structures, and a related simple exercise, is to create a maze (an array where each cell can have a horizontal and/or a vertical wall at one side), and a disjoint-set data structure with a node per maze cell, to quickly find out if each cell in the maze is reachable from every other cell; and when not, how many such disjoint zones there are in the maze. This also works as a step into maze-solving and shortest-path algorithms, which are fun and interesting. The array form is much faster than the pointer/forest form.For visualization of these, I recommend either SVG (if only generating visualization images), or theNetPBMP6 (binary) image format (which is easy to write, read, and modify); I've shown example functions to read and write PPM P6 files in Cin this post here.The SVG output is particularly fun if you want to experiment with non-rectangular cells, like triangular or hexagonal lattices, or even irregular meshes using say a Delaunay triangulation of a set of more or less uniformly random points. Since SVG is just text (XML), they are easy to emit from a program, and one can use any browser to view the images. Here's an example of something closely related (and only 7k in file size, too):

author: DiTBho
author_rank: Super Contributor
timestamp: March 22, 2022, 10:17:04 am
content: Quote from: Nominal Animal on March 21, 2022, 01:13:28 amalso good for {path-planning, feasible planning, optimal planning, forward search, backward search, bidirectional search, value iteration, logic-based planning, STRIPS, plan graph, planning as satisfiability} , maybe also for {Polygonal, polyhedral, and semi-algebraic models Rigid-body kinematic trees and nonrigid transformations}.I don't have the budget to play with micro-robotics, but it looks like I won a kit from Lynxmotion, something like a 4-degree-of-freedom robotic arm, likethisone, of course with the cheapest set of motors, because you cannot be too luckyAnyway, cheap or not, it doesn't matter, I think this will offer a good opportunity to play with reverse cinematic and some of the above algorithms.That data-structure is very useful