在大多数情况下,会用到链表,但是一直都没有仔细深究过,今天抽点时间做了个实验。
在看内核源码时,会发现使用二级指针插入或删除节点的情况,比如在字符设备里面的__register_chrdev_region及在notifier_chain_register里面。如下:
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) if ((*cp)->major > major || ((*cp)->major == major && (((*cp)->baseminor >= baseminor) || ((*cp)->baseminor + (*cp)->minorct > baseminor)))) break; ... cd->next = *cp; *cp = cd;
当找到要插入的地方(*cp),其实cp是上个节点的next指针所存放的地址,修改cp其实是修改了上个节点的next指针的指向。将被插入元素的next指针指向*cp指向的位置(即下一个节点),然后将*cp指针改向,改指为cd(该句保证与之前的链表元素连接不间断)。
其实精髓在于修改指针的指向,跟C里面的传地址和传值一样。
自己写了一个程序来验证一下:
#include <stdio.h> #include <stdlib.h> struct linklist { int num; struct linklist *next; }; struct linklist head = {.num = 0, .next = NULL}; int main() { int i; struct linklist *tmp = NULL; struct linklist **ttmp = NULL; for(i = 1; i < 6; i += 2) { tmp = (struct linklist *)malloc(sizeof(*tmp)); tmp->num = i; tmp->next = head.next; head.next = tmp; } ttmp = &(head.next); while(*ttmp) { printf("%d, %016x, %016x, %016x ", (*ttmp)->num, ttmp, *ttmp, (*ttmp)->next); ttmp = &((*ttmp)->next); } printf("============================ "); struct linklist addnode = {.num = 2, .next = NULL}; ttmp = &(head.next); while(*ttmp) { if ((*ttmp)->num < addnode.num) { break; } ttmp = &((*ttmp)->next); } addnode.next = *ttmp; *ttmp = &addnode; ttmp = &(head.next); while(*ttmp) { printf("%d, %016x, %016x, %016x ", (*ttmp)->num, ttmp, *ttmp, (*ttmp)->next); ttmp = &((*ttmp)->next); } ... return 0; }
运行结果如下:
这种方法还适用于删除链表节点的场景,代码类似如下:
void remove_if(node ** head, remove_fn rm) { for (node** curr = head; *curr; ) { node * entry = *curr; if (rm(entry)) { *curr = entry->next; free(entry); } else curr = &entry->next; } }
这种方法避免了传统的删除或插入链表节点需要记录链表prev节点的步骤,如下:
// Remove all nodes from the supplied list for which the // supplied remove function returns true. // Returns the new head of the list. node * remove_if(node * head, remove_fn rm) { for (node * prev = NULL, * curr = head; curr != NULL; ) { node * const next = curr->next; if (rm(curr)) { if (prev) prev->next = next; else head = next; free(curr); } else prev = curr; curr = next; } return head; }