Introduction:
A linked list is one of the important members of a data structure. Because of its simple structure, dynamic insertion, deletion of nodes, the advantages of less time, the list in the development of the application of many scenarios. Second only to arrays (the more simple the wider the application).
But. As with its strengths, the drawbacks of the list are obvious. It is not, of course, the random access to those things, but because the construction of the list (in a struct in which the same type of pointer) makes the list itself logical operations (such as adding nodes, delete nodes, query nodes, etc.), often with its application scenarios in the business data mixed with each other. This causes us to use the list to be handmade each time, the person who has done the list certainly has the deep understanding to this.
Is it possible to abstract a linked list from a transformed, unpredictable business data? The answer is yes. The list of links used in the Linux kernel is one such thing, and this article will take you one step further from the traditional list. Analyze the process of abstraction and finally get a conclusion as to why you should do so and why you can do so.
Just a little bit more emphasis. is to analyze its thinking process, so the code given in this article is mostly verifiable, not very complete. There is not much application value. Let's not dwell on it.
Traditional linked list:
This people should be very familiar with. Not much to say, here is a way to define the node and the implementation of the interface:
/*** the node and implementation interface of the traditional linked list */typedef structnode_trad{ //Traditional linked table node int n_i; Double D_j; Char ch_arr_k[32]; //... Changing unpredictable business data struct node_trad* next; NT; int list_create (nt**p_head); Constructs a traditional linked list int list_insert (nt*p_head, int pos, int i, double J, Char *k/*...*/); Insert a node int list_delete (nt*p_head, int pos); Delete a node int list_entry (nt*p_head, int pos, NT *dest); Query node int List_destroy (nt**p_head); Destroy a linked list </span>
The implementation code is not given here for space reasons.
From which we can out the shortcomings of the traditional list. Especially on the Insert interface function. What is more serious is that every time we change business data we have to construct code again, there is no reusability at all!
So what is the reason for preventing the reuse of the linked list? Obviously, because each business type has a different business node structure, they are all in memory: The business may require only a char as a data member, and that business may require hundreds of strings ... Our linked list pointers are stuck in the middle and cannot be uniformly manipulated at all. Therefore, in order to abstract out the list, we must unify the node aperture, that is, whatever the business model, our nodes are the same.
Such indirection is obviously the dish of the hands. (assuming that the transition is not smooth, the understanding of the pointer is less than the lack of advice to continue to practice.) Finally, I will recommend some books for you.
Just with the idea of a pointer, it solves a major ideological crisis.
How to do it in detail. How do we build our nodes? Just look at the following code:
Pointer linked list:
<spanstyle= "FONT-SIZE:14PX;" The node definition of the >/*** pointer list */typedef structnode_ptr{ struct node_ptr *next; Link pointer void *data; Business data pointer}np;
As you have seen. For the sake of narrative convenience. We give a list of such lists a name called a list of pointers. A pointer is included in the description.
As with the traditional linked list nodes, the link field is also included in the node of the pointer list, which is the next pointer, but the difference is that we no longer include a variety of business data in the node, but instead leave a pointer as the business data "mount" to the interface on the node. So, no matter what the business model looks like, we just need to point to it with a pointer. The following are the node connections for the two linked list models:
Watermark/2/text/ahr0cdovl2jsb2cuy3nkbi5uzxqv/font/5a6l5l2t/fontsize/400/fill/i0jbqkfcma==/dissolve/70/gravity /center ">
The above part of Figure 2 is our linked list model, no matter how the following business data changes, our list structure does not have to do whatever changes. The corresponding additions and deletions do not need to change. The data we face in a linked list is a pointer. In a professional parlance, business data is transparent to our list operations.
Well, the hype said for a long while, how to achieve it? Can it be achieved? Let's talk about it in code:
Header file of the linked list:
PtrList.h file #ifndef _ptrlist_h#define _ptrlist_h#include "string.h" #include "stdlib.h"/*** the node definition of the pointer list */typedef structnode_ptr{ struct node_ptr *next; Link pointer void *data; Business data pointer}np; int list_create (np**p_head); Create a new linked list int list_insert (np*p_head, void *p_data); Insert data into the chain footer int list_delete (np*p_head, void *p_data); Delete list specified item node void* list_entry (np*p_head, int pos); Returns the specified location node address int List_destroy (np**p_head);
Linked List implementation file:
ptrlist.c file #include "ptrList.h" int list_create (np**p_head) {//function: Create a new linked list, return its head node address assigned to *p_head NP *head = NULL; Build head node and initialize head = (np*) malloc (sizeof (NP)); if (head = = NULL) {return-1; } head->next = NULL; Head->data = NULL; The head node is returned with a *p_head = head; return 0;} int List_insert (np*p_head, void *p_data) {//function: Inserts incoming pointer parameters into the linked list last//description: P_data null does not do special processing NP *pcur = NULL, *PM = NULL; if (P_head = = NULL) {return-1; }//Build the node to be inserted PM = (np*) malloc (sizeof (NP)); if (PM = = NULL) {return-1; } pm->data = P_data; Pm->next = NULL; Looking for the last node pcur = P_head; while (pcur->next! = NULL) {pcur = pcur->next; }//Insert node Pcur->next = PM; return 0;} int List_delete (NP*p_head, void *p_data) {//function: Delete the first data item in the linked list that P_head points to p_data node//description: P_data is null do not do special processing NP *pcur = NULL; Point to the node to be deleted NP *ppre = NULL; Save pcur Previous node if (P_head = = NULL) {return-1; }//Search scene initialization ppre = P_head; Pcur = p_head->next; Loop Search while (Pcur!=null &&pcur->data!=p_data) {ppre = Pcur; Pcur = pcur->next; }//Run delete action if (pcur! = NULL) {//found Ppre->next =pcur->next; Free (pcur); } return 0;} void* list_entry (np*p_head, int pos) {//function: Returns the data item pointer//description for the POS node of the linked list: such as POS (min. 1) out of index range Returns a null NP *pcur = NULL; Points to target node int index = 1; Node count if (P_head==null | | pos<=0) {return NULL; }//Search scene initialization Pcur = p_head->next; Loop Search while (Index!=pos &&pcur!=null) {pcur = pcur->next; index++; }//Returns the result if (pcur = = null) {//did not find return NULL; } return pcur->data;} int List_destroy (np**p_head) {//function: Destroys a linked list//description: Place its head pointer null NP *pcur = NULL; Point to the node to be destroyed NP *pnxt = NULL; The next node to be destroyed if (P_head = = NULL) {return-1; }//Scene initialization pcur = *p_head; PNXT = (*p_head)->next; Loop Delete node while (pnxt! = NULL) {free (pcur); Pcur = PNXT; PNXT = pnxt->next; }//delete last node free (pcur); (*p_head) = NULL; return 0;}
In the linked list implementation file. We use void* to encapsulate pointers to real business models, which appear in the list. We are simply manipulating the void* pointer. The corresponding type conversions are then performed at the business layer. Get the type of pointer we want.
Test file in main function
MAIN.C file # include "Stdio.h" #include "string.h" #include "stdlib.h" #include "ptrList.h" typedef structtestdata{INT N Aie Double D_j; Char ch_arr_k[32]; }td;int Main () {//build LIST_PTR Test example int i = 0; NP *phead = NULL; TD TD1, TD2, TD3, TD4; Td1.n_i = 1; Td1.d_j = 1.1; strcpy (Td1.ch_arr_k, "Hi, I amtd1!"); Td2.n_i = 2; Td2.d_j = 2.2; strcpy (Td2.ch_arr_k, "Hi, I amtd2!"); Td3.n_i = 3; Td3.d_j = 3.3; strcpy (Td3.ch_arr_k, "Hi, I amtd3!"); Td4.n_i = 4; Td4.d_j = 4.4; strcpy (Td4.ch_arr_k, "Hi, I amtd4!"); Create a new list_ptr linked list list_create (&phead); Add the use case to the list List_insert (Phead, (void*) &TD1); List_insert (Phead, (void*) &TD2); List_insert (Phead, (void*) &td3); List_insert (Phead, (void*) &td4); Delete the second element List_delete (Phead, (void*) &TD2); Traverse the list with lookup and print for (i=0; i<3; i++) {TD *ptd = (td*) list_entry (Phead, i+1); printf ("%d\n", ptd->n_i); printf ("%lf\n", Ptd->d_j); printf ("%s\n", ptd->ch_arr_k); }//Destroy linked list List_destroy (&phead); System ("pause"); return 0;}
In the main function file, we first build a test-tested business model, and then add several of its objects to the list we make. Note that the increment is converted to void* to match the underlying implementation of the list, and then reverse-convert on the query results to get the type we want. And then perform the corresponding operation on it. That is, the void* pointer is passed between the business layer and the bottom level, which is used frequently in the design of the underlying library. Attention should be brought.
This is not the end of the story, let's analyze the two parts of the pointer list, two pointers: one is the link field indicates that it is a linked list node, and the other is a data pointer field, which is used to achieve its relationship with business data data.
Other words. Give us a node where we can track down the next node and be able to connect to the business data through a pointer.
This is not talking about tongue twisters. is to let people value this logical relationship.
Next we have to consider one thing: is it possible to use a pointer to complete two goals. Can you find the next node and be able to contact the business data? Since there are only two of them. Let's take a look at it by turns.
Suppose that you delete a link field pointer. So the question is clearly coming, how do you find the next node? Let's say we're standing next to the memory with a pointer inside. Looking past this pointer is a business model. Suppose there is just one pointer pointing to the next node in this model ... This is a traditional linked list model. Now that there is no next node information in the business model, we can just go back to the pointer and find out where? Look on it. Take a look at the ... Well, by the way, we can just look up or look down on the basis of the pointer's address.
It would be nice to assume that the following is exactly the pointer to the next node.
Yes, because we have removed the link domain, we can only store each node pointer in sequential storage mode.
Of course this is not called the list, it has a fixed name, perhaps you have recognized it, it is an array of pointers.
Suppose the data pointer field is deleted. So the question comes again, how to find the corresponding business data of this node? We have to re-come to the memory of the world, to stand in the memory of the pointer to the block before, where to find it? Look, take a look ... Hey. Let's say we're lucky enough to have the business data right next to the knot. However, the Linux kernel designers tell us that success is not a fluke.
Kernel Linked list:
The problem is very easy, we just have to put the business data next to the pointer. The next method is to use the pointer as a member variable (the essence) of the business data to allocate space with other data. This also forms the construction method of the Linux kernel chain list. Put a pointer in each of the business nodes. But this pointer does not point to the next node address like a traditional linked list. Instead, it refers to the pointer to the next node.
Its connection model is as follows:
See. This seems to have little to do with the traditional connection model, but there are different operational logic. The main fact is that we are able to pull the internal link domain out of the business data and make a separate list operation. In contrast to another list of pointers, it eliminates the construction of the end point (which is built when the business data is built), and the list operation is simply an operation on one of the existing pointer nodes. (Note this point)
Let's go back to the part of the tongue twister. Can we restore the data from the point of the pointer to the node of the business? Here is a relatively low level of knowledge, that is, the structure of the memory storage method problem. Now that we've spoken, let's say a few more words, our process of data access to structures is controlled by offsets, and when C or C + + programs finally convert assembly code, all variable information does not exist. There is no longer a definition of int n, double F, char c. Instead, there are different offsets and different memory sizes.
So when we're writing down p->a. (Suppose P points to a struct.) Containing a variable named a) the compiler understands that the actual is * (P+X). where x is the offset of a. Let's go back to the *next node, after we find this node. Equivalent to knowing an address about a business node, assuming that the pointer is placed in the first position. The location is the address of the business node. You can access the corresponding data in the business node in the form of (data*) (next)->a.
Suppose the first node is not placed. That also adds an offset to determine the first address of the business node, which is what the real Linux kernel list does, and it defines the linked list nodes as two-way, and the ability to place the linked list nodes wherever the business model is located.
The offset is calculated by the way the macro defines the type of transmission. It belongs to the very bottom of the application of knowledge, interested in being able to see the source code.
For a variety of reasons. The source of the kernel list is not given here. Give a similar simplified code:
Kernel-Linked header file:
KernelList.h file #ifndef _kernellist_h#define _kernellist_h typedef structnode_kernel{ struct Node_kernel *next;} NK; int list_create (nk**p_head); Constructs a list of int list_insert (Nk*p_head, NK *p_insert); Inserts a new node at the end of the list int list_delete (nk*p_head, NK *p_delete); Deletes the specified element void* list_entry (nk*p_head, int pos); Returns the address of the linked list node at the specified location int List_destroy (nk**p_head); Destroy linked list #endif
Kernel linked list implementation file:
Kernellist.c#include "KernelList.h" #include "string.h" #include "stdio.h" #include "stdlib.h" int list_create (NK**P_ Head) {//construct a linked list//function: Construct a linked list NK *pm = NULL; if (P_head = = NULL) {return-1; } PM = (nk*) malloc (sizeof (NK)); if (PM = = NULL) {return-1; } pm->next = NULL; *p_head = PM; return 0;} int List_insert (nk*p_head, NK *p_insert) {//function: Insert a new node at the end of the list NK *pcur = null;//point to the End node if (p_head= =null | | P_insert==null) {return-1; }//Scene initialization pcur = P_head; Loop lookup while (pcur->next! = NULL) {pcur = pcur->next; }//Insert Pcur->next = P_insert; return 0;} int List_delete (Nk*p_head, NK *p_delete) {//function: Delete the specified element NK *pcur = NULL; Point to the deleted element NK *ppre = NULL; Point to the deleted element before an if (P_head==nulL | | P_delete==null) {return-1; }//Scene initialization ppre = P_head; Pcur = p_head->next; Loop lookup while (Pcur!=p_delete &&pcur!=null) {ppre = Pcur; pcur=pcur->next; }//Delete if (pcur = = NULL) {//not found, return directly 0; } Ppre->next = pcur->next; return 0; }void* list_entry (nk*p_head, int pos) {//function: Returns the address of the linked list node at the specified location//description: such as POS exceeding the index range (minimum 1). The null NK *pcur = NULL is returned; Point to the node to find int index = 1; Node Count if (P_head = = null) {return null; }//Scene initialization pcur = p_head->next; Loop lookup while (pcur! = NULL && index!=pos) {pcur = pcur->next; index++; }//returns return pcur; Not found Pcur for Null}int List_destroy (nk**p_head) { Function: Destroy chain list//Description: Note Only need to release the head node can be, do not release the back of the Node free (*p_head); *p_head = NULL; return 0;}
Main function test file:
#include "stdio.h" #include "string.h" #include "stdlib.h" #include "kernelList.h"/** chain table type such as the following * * typedef STRUCTKERNEL_ node{* struct kernel_node *next;*}kn;* int list_create (kn**p_head); Constructs a linked list * int List_insert (kn*p_head, KN *p_insert); Insert a new node at the end of the list * int list_delete (kn*p_head, KN *p_delete); Delete the specified element * void* list_entry (kn*p_head, int pos); Returns the address of the linked list node at the specified location ***/typedef structtestdata{NK nk;<spanstyle= "White-space:pre" > < The/span>//is placed in the first position, omitting the process of calculating the offset int n_i; Double D_j; Char ch_arr_k[32];} Td;int Main () {//build LIST_PTR Test example int i = 0; NK *phead = NULL; TD TD1, TD2, TD3, TD4; Td1.nk.next = NULL; Td1.n_i = 1; Td1.d_j = 1.1; strcpy (Td1.ch_arr_k, "Hi, I amtd1!"); Td2.nk.next = NULL; Td2.n_i = 2; Td2.d_j = 2.2; strcpy (Td2.ch_arr_k, "Hi, I amtd2!"); Td3.nK.next = NULL; Td3.n_i = 3; Td3.d_j = 3.3; strcpy (Td3.ch_arr_k, "Hi, I amtd3!"); Td4.nk.next = NULL; Td4.n_i = 4; Td4.d_j = 4.4; strcpy (Td4.ch_arr_k, "Hi, I amtd4!"); Construct a linked list list_create (&phead); Insert Element List_insert (Phead, (nk*) &TD1); List_insert (Phead, (nk*) &TD2); List_insert (Phead, (nk*) &td3); List_insert (Phead, (nk*) &td4); Delete the second element List_delete (Phead, (nk*) &TD2); Deletes the specified element//loop output for (i=0; i<3; i++) {TD *ptd = (td*) list_entry (Phead, i+1); printf ("%d\n", ptd->n_i); printf ("%lf\n", Ptd->d_j); printf ("%s\n", ptd->ch_arr_k); }//Destroy linked list List_destroy (&phead); System ("pause"); return 0;}
One of the easy mistakes in implementing a linked list model is trying to free its node memory. Formally, the number of malloc/new and Free/delete in a set of libraries should be equal.
We do not allocate memory when inserting nodes, so we should not have memory when we delete nodes. Otherwise it's going to go wrong. In addition, the application of memory in this module is best released within the module, or easy error.
For the question itself. We can see from the connection of the junction as well as the previous narrative. Nodes are allocated on the stack along with the business data, and we simply define a head node. and use a function to connect it together. It is certainly wrong to release the memory on the stack.
Written at the end:
The discussion about the list is over. From the traditional linked list to the pointer linked list to the final kernel linked list, take everyone to go through the complex business logic of the process of abstracting the model. The main use of the pointer to the transparency and variables in memory by offset index and other ideas. These ideas are more inclined to the bottom. Some of the related books are introduced, such as the reference to pointers in the various object models in the deep exploration of the C + + object model, and the function pointers in the polymorphic implementation process. Is the idea of such transparency.
and the variable storage method of knowledge is very much in the book to be involved in the "deep understanding of computer systems," the book is very specific.
Interested cheese can go and have a look. Finally, personal ability is limited, there are fraught places also ask everyone to criticize correct.
PS: The code in this article is all tested in VS2010.
The art of linked list--linux kernel chain list analysis