list.h头文件详解

编程入门 行业动态 更新时间:2024-10-26 11:18:40

list.h头文件<a href=https://www.elefans.com/category/jswz/34/1770044.html style=详解"/>

list.h头文件详解

这两天看了下/usr/src/linux/list.h文件,感受颇多,里面主要讲了两种链表:双向循环链表和哈希链表,以及他们的一些基本的操作!下面来和大家分享下我的分析过程:(申明:我以下分析基于的内核版本是:2.6.32-24)
 

19 struct list_head {   20 struct list_head *next, *prev;   21 };
 
这里定义了一个list_head的结构体,它是双向循环链表的节点结构体,里面并无数据成员,就只有两个指向list_head型结构体的指针,分别指向其前驱节点和后继节点,因此,这个结构体的主要作用就是嵌套在其他结构体的定义中起到链接作用。例如: struct student{ int id; char name[20]; struct list_head member; }; member是一个list_head型的结构体,同时又是student型结构体的成员,利用member.prev和member.next就可以访问到和与之相连的另外两个student结构体的member成员。 
 23 #define LIST_HEAD_INIT(name) { &(name), &(name) } 24  25 #define LIST_HEAD(name) \ 26     struct list_head name = LIST_HEAD_INIT(name)
这三行的意思是利用两个宏来定义并初始化一个list_head型的结构体name,首先来看下面这个宏,他定义了name
这个结构体,并利用上面的宏为其赋值,而上面的宏的意思是将一个结构体自身赋给它的前驱节点和后继节点,这样便完成了定义并初始化的功能。
 
28 static inline void INIT_LIST_HEAD(struct list_head *list)   29 {   30 list->next = list;   31 list->prev = list;   32 }
虽然上面的两个宏提供了定义并初始化一个list_head类型的结构体的功能。但是系统还是提供了这个单独进行初始化的函数。在这里我们简单解释下static和inline的意思: static修饰的函数就是静态函数,是对函数作用域的限制,指该函数的作用域仅限于本文件,不能被其他文件使用,因此static具有信息隐藏作用。 inline修饰的函数的意思是这个函数对编译程序是可见的,编译程序在调用这个函数时就立即展开该函数,而不是保存现场,执行调用函数,恢复现场等繁琐操作,这样可以提高效率。这样的函数一般称之为“内联函数”,但是注意:inline必须和函数定义体放在一起才能使函数成为内联函数,一般放于头文件中。
 
40 #ifndef CONFIG_DEBUG_LIST
 41 static inline void __list_add(struct list_head *new,42                   struct list_head *prev,43                   struct list_head *next)44 {45     next->prev = new;46     new->next = next;47     new->prev = prev;48     prev->next = new;49 }50 #else51 extern void __list_add(struct list_head *new,52                   struct list_head *prev,53                   struct list_head *next);54 #endif
 
这几句是一个条件编译的语句,大体的意思就是如果没有定义CONFIG_DEBUG_LIST这个宏就编译下面static inline void __list_add()函数,else就使用extern声明此函数已经在外部文件中定义过了,防止重复定义!
我们来看下这个函数是干什么的?其实我的建议是大家在看此类链表代码时找个草稿本在上面一边看一边花,这样很简单明了,语言解释反倒麻烦,我就不罗嗦了,它的意思就是在原本相连的prev和next两个节点之间增加一个new节点。
 
 64 static inline void list_add(struct list_head *new, struct list_head *head)   65 {   66 __list_add(new, head, head->next);   67 }
 
78 static inline void list_add_tail(struct list_head *new, struct list_head *head) 79 { 80     __list_add(new, head->prev, head); 81 }
这两个函数由上面的 __list_add()函数衍生而来,其实后面很多都是如此,复杂的函数都是由前面定义的基本函数组合而来。这两个函数也不用罗嗦了,大概的意思就是分别在head节点的后面和前面增加一个节点new,在head节点后面增加即增加在链表头部,在head前面增加即增加在链表尾部!(注意:循环链表的特点!)
90 static inline void __list_del(struct list_head * prev, struct list_head * next) 91 { 92     next->prev = prev; 93     prev->next = next; 94 }
这又是一个基本函数,意思是将形参传来的两个节点链接起来。
102 #ifndef CONFIG_DEBUG_LIST 103 static inline void list_del(struct list_head *entry) 104 { 105     __list_del(entry->prev, entry->next); 106     entry->next = LIST_POISON1; 107     entry->prev = LIST_POISON2; 108 } 109 #else 110 extern void list_del(struct list_head *entry); 111 #endif
还是一个条件编译语句,我们直接看函数定义,此函数是一个删除节点函数,它首先利用上面的基本函数将entry所指的节点的前驱节点和后继节点链接起来,然后将entry节点的前驱指针和后继指针分别指向两个宏:LIST_POISON1,LIST_POISON2,在poison.h文件中,找到了这两个宏的定义: 22 #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA) 23 #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA) POISON_POINTER_DELTA宏又是什么呢?同样在在那个文件中: 11 #ifdef CONFIG_ILLEGAL_POINTER_VALUE 12 # define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL) 13 #else 14 # define POISON_POINTER_DELTA 0 15 #endif 这样我们就知道POISON_POINTER_DELTA其实就是0,那么LIST_POISON1 ,LIST_POISON2,这两个宏就很轻松理解,其实就是两个固定的地址。
120 static inline void list_replace(struct list_head *old, 121                 struct list_head *new) 122 { 123     new->next = old->next; 124     new->next->prev = new; 125     new->prev = old->prev; 126     new->prev->next = new; 127 }
这个函数是实现用new节点来替换old节点,但是我们细心便会发现它的缺陷,它虽然实现了替换,但是old节点依然在那条链表上,即没有取消old节点的前驱指针和后继指针的指向。
129 static inline void list_replace_init(struct list_head *old, 130                     struct list_head *new) 131 { 132     list_replace(old, new); 133     INIT_LIST_HEAD(old); 134 }
因为上面的函数存在缺点,所以出现了这个函数,它在实现替换功能后,使用了INIT_LIST_HEAD()函数,我们回头看下这个函数的定义,便知道是将old节点的前驱节点和后继节点全部指向自己!以做到彻底的替换!
140 static inline void list_del_init(struct list_head *entry) 141 { 142     __list_del(entry->prev, entry->next); 143     INIT_LIST_HEAD(entry); 144 }
这个函数是一个删除节点的函数,首先将entry的前驱节点和后继节点链接起来,再将entry节点的前驱和后继指针分别指向自己,以做到彻底删除!后面的组合函数愈来愈多,我的感受是第一次看肯定记不住前面那么多函数的功能,那么就即时的回头去看,慢慢就熟悉,记住了。
151 static inline void list_move(struct list_head *list, struct list_head *head) 152 { 153     __list_del(list->prev, list->next); 154     list_add(list, head); 155 }
这个函数就是先将list节点从原链表中删除,然后将其添加到head链表的表头即head节点的下一个节点!
162 static inline void list_move_tail(struct list_head *list, 163                   struct list_head *head) 164 { 165     __list_del(list->prev, list->next); 166     list_add_tail(list, head); 167 }
类似于上面的函数,先将list节点从原链表中删除,在将其添加到head链表的表尾即head的前一个节点!(注意理解双向循环链表!)
174 static inline int list_is_last(const struct list_head *list, 175                 const struct list_head *head) 176 { 177     return list->next == head; 178 }
这个函数是测试list节点是否为head链表的表尾节点。是返回1,否则返回0.
184 static inline int list_empty(const struct list_head *head) 185 { 186     return head->next == head; 187 }
这个函数是测试head链表是否为空链表。是返回1,否则返回0.(此函数存在缺陷,看下面它的仔细版:)
202 static inline int list_empty_careful(const struct list_head *head) 203 { 204     struct list_head *next = head->next; 205     return (next == head) && (next == head->prev); 206 }
看函数名和上面的很相似,它完善了上面函数的缺陷,它检查空链表时更加”仔细“,它判断空链表有两个条件: 1,上面函数的条件,即head的后继节点就是本身。 2,head的前驱节点和后继节点相同。 这两个条件一起就可以确定此链表是否为空链表。
212 static inline int list_is_singular(const struct list_head *head) 213 { 214     return !list_empty(head) && (head->next == head->prev); 215 }
这个函数判断head链表是否为单节点链表。
217 static inline void __list_cut_position(struct list_head *list, 218         struct list_head *head, struct list_head *entry) 219 { 220     struct list_head *new_first = entry->next; 221     list->next = head->next; 222     list->next->prev = list; 223     list->prev = entry; 224     entry->next = list; 225     head->next = new_first; 226     new_first->prev = head; 227 }
这个函数是将head链表的头结点至entry节点之间的节点连在list节点后面,即组成以list节点为头结点的新链表。
243 static inline void list_cut_position(struct list_head *list, 244         struct list_head *head, struct list_head *entry) 245 { 246     if (list_empty(head)) 247         return; 248     if (list_is_singular(head) && 249         (head->next != entry && head != entry)) 250         return; 251     if (entry == head) 252         INIT_LIST_HEAD(list); 253     else 254         __list_cut_position(list, head, entry); 255 }
这个函数的功能和上面的函数相同,但是判断的更加仔细,首先它排除了空链表的情况,下来又否定了单链表而且节点不是entry的情况,然后又判断了entry和head指向同一节点的情况,这种情况下按照功能,只需要初始化list链表即可。最后在排除晚其他不安全情况后进行了“切割移植”操作。
257 static inline void __list_splice(const struct list_head *list, 258                  struct list_head *prev, 259                  struct list_head *next) 260 { 261     struct list_head *first = list->next; 262     struct list_head *last = list->prev; 263  264     first->prev = prev; 265     prev->next = first; 266  267     last->next = next; 268     next->prev = last; 269 }
这个函数的意思是将list链表的全部节点(头结点list除外)插入在prev和next节点之间。
276 static inline void list_splice(const struct list_head *list, 277                 struct list_head *head) 278 { 279     if (!list_empty(list)) 280         __list_splice(list, head, head->next); 281 }
这个函数的意思就是在list是非空链表的情况下,将其插在head链表的头部,即head节点的后面。此函数不安全,因为在插入后还能通过list节点访问到其余的节点。
288 static inline void list_splice_tail(struct list_head *list, 289                 struct list_head *head) 290 { 291     if (!list_empty(list)) 292         __list_splice(list, head->prev, head); 293 }
与上面的函数类似,在list是非空链表的情况下,将其插在head链表的尾部,即head节点的前面。不安全,原因同上!
302 static inline void list_splice_init(struct list_head *list, 303                     struct list_head *head) 304 { 305     if (!list_empty(list)) { 306         __list_splice(list, head, head->next); 307         INIT_LIST_HEAD(list); 308     } 309 }
这个函数的意思就是在list是非空链表的情况下,将其插在head链表的头部,即head节点的后面。然后对list节点进行初始化,排除不安全因素。
319 static inline void list_splice_tail_init(struct list_head *list, 320                      struct list_head *head) 321 { 322     if (!list_empty(list)) { 323         __list_splice(list, head->prev, head); 324         INIT_LIST_HEAD(list); 325     } 326 }
这个函数的意思就是在list是非空链表的情况下,将其插在head链表的尾部,即head节点的前面。然后对list节点进行初始化,排除不安全因素。
334 #define list_entry(ptr, type, member) \ 335     container_of(ptr, type, member)
真正精彩的地方到了,这个宏以及下面的几个宏的定义,我感觉很精彩!有些地方初次理解起来比较费力,我在这里为大家慢慢分享下我的理解,不一定对,有错误的地方希望大家赐教!不罗嗦了,我们看上面这个宏,这其实就是一个宏定义的转移,利用container_of这个宏定义了上句的list_entry这个宏,我们还是不懂意思,不急。我们在kernel.h里面找到container_of这个宏的定义: 653 #define container_of(ptr, type, member) ({          \ 654     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 655     (type *)( (char *)__mptr - offsetof(type,member) );}) 这个里面又用到了offsetof的宏定义,我们在stddef.h里面找到它的定义: 24 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 下来,我们来综合分析下,我们首先分析offsetof的意思:我们再看一遍它的定义,((size_t) &((TYPE *)0)->MEMBER) 最外面的括号里面分为两部分:(size_t)    和    & ((TYPE *)0)->MEMBER,我们首先看& ((TYPE *)0)->MEMBER,它由两部分组成,&和 ((TYPE *)0)->MEMBER,我们先看后者((TYPE *)0)->MEMBER,他首先将0强制类型转换成指向TYPE类型的结构体的指针,然后引用其MEMBER成员。可能有些人不懂,这样,我们来随便定义一个TYPE类型的结构体: struct TYPE { int a; char b; int c; struct list_head MEMBER; }; 这样看起来是不是一目了然,现在我们来看offsetof的意思,很简单就是:首先将0强制类型转换成指向TYPE类型的结构体的指针,然后取其MEMBER成员的地址,最后再强制类型转换成size_t行,即(unsigned int)型!那么有人会问了,为什么要使用0呢?要它的MEMBER成员的地址有什么用呢?用处很大,而且很巧!我们需要慢慢品尝: 首先,我们回顾下,什么叫指针?指针就是一个内存单元的地址!那么好,我们上面说到了,将0强制转换成TYPE类型结构体的指针,那么我们来说说,这个所指的结构体的起始地址是多少?理所当然是0.那么很好,既然它的起始地址是0,那么它的MEMBER成员的地址,其实就是它在这个结构体的偏移量!理解了吗?OK!offsetof(TYPE, MEMBER)的意义我们知道了,就是求TYPR类型的结构体中MEMBER成员的偏移量!它的作用,我们继续往下看: 知道了offsetof的作用,我们返回去看container_of这个宏的定义: #define container_of(ptr, type, member) ({          \ const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ (type *)( (char *)__mptr - offsetof(type,member) );}) 它的定义分两句话,先看第一句:const typeof( ((type *)0)->member ) *__mptr = (ptr);我们从左开始慢慢看,首先看typeof()里面的东西:((type *)0)->member ,它什么意思呢?相比大家都知道了,它就是以0为起始地址的type类型的结构体的member成员(还没理解的,分析过程见上面的offsetof分析过程!)。那么typeof的含义是什么呢? typeof的含义都能看出来,就是提取类型的意思!那么等号左面const typeof( ((type *)0)->member ) *__mptr的意思就是:定义一个指向以0为起始地址的type类型的结构体的member成员的类型的指针_mptr,那么第一句的意思很明了,就是定义这么一个指针,并将ptr指针的值赋给它!那么ptr是什么呢?根据这个宏的形参,我们知道它是指向type类型结构体中member成员的指针,那么现在_mptr也指向了type类型结构体中member成员。 好,我们看下一句:(type *)( (char *)__mptr - offsetof(type,member) );offsetof宏的定义我们知道了,-mptr 的意思我们也知道了,那么这句话应该不难,就是先将指针_mptr强制类型转换成(char *),再用他减去type类型的结构体中member成员的偏移量,最后将结果强制转换成(type *)类型!可能有些人不理解这句话背后的意思,别急,我们慢慢分析,由于_mptr是指向type类型结构体中member成员的指针,那么她的值就是type类型结构体中member成员的地址,而offsetof(type,member)的意思是type类型的结构体中member成员的偏移量,那么用它的地址减去它的偏移量,是什么结果呢?这是个数学问题!用一个结构体中一个成员的地址减去它在这个结构体中的偏移量,等于?很简单,等于这个结构体的起始地址!好!那么有人会问,为什么要转换成(char *)类型再减呢? 这是一个关于指针加减的问题!就是如果前面的指针不是(char *)等单字节类型,假设是(int *)类型,那么用它家去一个偏移量,就等于减去了(偏移量*sizeof(int)个单元,这是为什么呢?因为指针在进行加减时是以减数类型的大小为基准进行加减的,就是说如果是(int *)类型,那么它实际会以int类型为单元减去偏移量个!不就是减去了(偏移量*sizeof(int)个单元吗?这样就错了 !为此我专门写了一个简单的函数进行验证: 1 #include<stdio.h> 2 int main() 3 { 4     struct student 5     { 6         int a; 7         char b; 8         int c; 9         int d; 10     }; 11     struct student *p; 12     struct student q; 13     p=&q;                                                                         //用p指向这个结构体! 14     printf("&struct=%u\n",(unsigned long)p);                        //打印这个结构体的地址! 15     printf("&q.d   =%u\n",(unsigned long)&(q.d));                  //打印这个结构体中d变量的地址 16     printf("   true=%u\n",(unsigned long)(char *)&(q.d)-12);   //打印d变量转换成(char *)后减去它的偏移量到的结构体的地址!(是正确的!) 17     printf("  false=%u\n",(unsigned long)(&(q.d)-12));        //打印没有转换进行减法得到的值,是错误的!而且正是我们分析的减了偏移量*sizeof(int),所以是错的! 18 } 运行结果是:(有编译警告,先不管!直接运行!) [wjl@wjl-desktop ~/c/test 21:38:58 #27]$ ./test &struct=3213925676 &q.d   =3213925688 true=3213925676        //3213925688-12 false=3213925676        //3213925676-12*4 大家仔细观察这个程序便会发现指针加减的问题!验证我上面的说法!ok! 那么到此为止,我们分析接近尾声了,container_of(ptr, type, member)宏的意思出来了,就是已知指向type类型的结构体中member成员的指针ptr的情况下,我们求出了这个结构体的起始地址!要这个地址有什么用呢?很多人可能都想到了,指针!好,我们往下看!
345 #define list_first_entry(ptr, type, member) \ 346     list_entry((ptr)->next, type, member)
这个宏的意思是不是很简单了,就是已知type类型的结构体中member成员的指针后,我们 可以求得它所在的链表的下一个指针所指的member所在的type类型的结构体的起始地址!
353 #define list_for_each(pos, head) \ 354     for (pos = (head)->next; prefetch(pos->next), pos != (head); \ 355             pos = pos->next)
这个宏的意思是:从head节点开始(不包括head节点!)遍历它的每一个节点! 在这里我们说下:prefetch(pos->next)这句话,它的意思就是告诉cpu下一个要使用的内存!以提高效率!
367 #define __list_for_each(pos, head) \ 368     for (pos = (head)->next; pos != (head); pos = pos->next)
这个函数和上面的函数大同小异,功能相同,只是它没有预取下一个要遍历的节点!所以效率上差点!所以它比较适合节点数比较少的链表!
375 #define list_for_each_prev(pos, head) \ 376     for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ 377             pos = pos->prev)
看完上面的函数,这个就比较简单,它也是从head节点开始(不包括head节点)向前遍历每一个节点!即从链表的尾部开始遍历!
385 #define list_for_each_safe(pos, n, head) \ 386     for (pos = (head)->next, n = pos->next; pos != (head); \ 387         pos = n, n = pos->next)
这个应该也没有难度,只是操作更加安全,它用n先将下一个要遍历的节点保存起来,防止程序中删除本节点后,无法找到下一个节点,而出现错误!
395 #define list_for_each_prev_safe(pos, n, head) \ 396     for (pos = (head)->prev, n = pos->prev; \ 397          prefetch(pos->prev), pos != (head); \ 398          pos = n, n = pos->prev)
这个应该也没有难度,只是操作更加安全,它用n先将下一个要遍历的节点保存起来,防止程序中删除本节点后,无法找到下一个节点,而出现错误!
406 #define list_for_each_entry(pos, head, member)              \ 407     for (pos = list_entry((head)->next, typeof(*pos), member);  \ 408          prefetch(pos->member.next), &pos->member != (head);    \ 409          pos = list_entry(pos->member.next, typeof(*pos), member))
又是一个很长的宏定义!我们一句一句看for()里面的三条语句,第一句:pos = list_entry((head)->next, typeof(*pos), member),list_entry(ptr,type,member)宏的意思是:已知指向type类型的结构体中member成员的指针ptr的情况下,我们求出了这个结构体的起始地址,所以这里我们进行类比分析,我们已知一个结构体的类型是typeof(*pos)类型!为什么是这个类型呢?因为pos是指向某一个结构体的指针,那么*pos不就是这个结构体吗? 继而typeof(*pos)不就是这个结构体的类型吗?OK!那么这条语句的意思就是一个结构体的指针pos,以及指向它中的member成员的指针(head->next),我们求出这个结构体的起始地址,并赋予pos!即让pos指向与之相连的下一个结构体!这里有些人可能纳闷了,结构体怎么相连了?还记得我们刚开始分析struct list_head这个类型时举的例子吗? struct student{ int id; char name[20]; struct list_head member; }; 我们来看这个例子,结构体虽然不能相连,但是结构体中的member成员却是list_head类型的结构体,他是可以组成链表的!第三句和第一句相同!不罗嗦了! 总之,这个宏的意思就是已知指向某个结构体的指针pos,以及指向它中member成员的指针head,从下一个结构体开始向后遍历这个结构体链。
417 #define list_for_each_entry_reverse(pos, head, member)          \ 418     for (pos = list_entry((head)->prev, typeof(*pos), member);  \ 419          prefetch(pos->member.prev), &pos->member != (head);    \ 420          pos = list_entry(pos->member.prev, typeof(*pos), member))
原理同上,作用就是:已经指向某个结构体的指针pos,以及指向它中member成员的指针head,从下一个结构体开始向前遍历这个结构体链。
430 #define list_prepare_entry(pos, head, member) \ 431     ((pos) ? : list_entry(head, typeof(*pos), member))
这个宏比较简单,它就是判断pos这个指针是否为空,为空的话给它赋值list_entry(head, typeof(*pos), member)这条语句求出来的结构体的地址!具体的求法看上面的分析!
442 #define list_for_each_entry_continue(pos, head, member)         \ 443     for (pos = list_entry(pos->member.next, typeof(*pos), member);  \ 444          prefetch(pos->member.next), &pos->member != (head);    \ 445          pos = list_entry(pos->member.next, typeof(*pos), member))
这句话的分析基本和上面那个相同,它的意思就是:已知指向某个结构体的指针pos,以及指向它中的member成员的head指针,从它的下一个结构体开始向后遍历这个结构体链!
456 #define list_for_each_entry_continue_reverse(pos, head, member)     \ 457     for (pos = list_entry(pos->member.prev, typeof(*pos), member);  \ 458          prefetch(pos->member.prev), &pos->member != (head);    \ 459          pos = list_entry(pos->member.prev, typeof(*pos), member))
这个函数和上面的函数基本相同,只是它是向前遍历,即从结构体链的尾部开始遍历!
469 #define list_for_each_entry_from(pos, head, member)             \ 470     for (; prefetch(pos->member.next), &pos->member != (head);  \ 471          pos = list_entry(pos->member.next, typeof(*pos), member))
这个函数和上面的函数基本相同,只是它是从前向后遍历,而且从pos所指的节点开始!(包括pos所指的节点!)
480 #define list_for_each_entry_safe(pos, n, head, member)          \ 481     for (pos = list_entry((head)->next, typeof(*pos), member),  \ 482         n = list_entry(pos->member.next, typeof(*pos), member); \ 483          &pos->member != (head);                    \ 484          pos = n, n = list_entry(n->member.next, typeof(*n), member))
这个安全的函数和上面的安全函数大同小异!原理相同,都是先保存下一个要遍历的节点!防止本节点被删除,下一节点无法访问,导致错误!
496 #define list_for_each_entry_safe_continue(pos, n, head, member)         \ 497     for (pos = list_entry(pos->member.next, typeof(*pos), member),      \ 498         n = list_entry(pos->member.next, typeof(*pos), member);     \ 499          &pos->member != (head);                        \ 500          pos = n, n = list_entry(n->member.next, typeof(*n), member))
原理同上,不罗嗦了!只是增加了一个指向结构体的指针,在for()里面的第一句就赋值给他让它指向pos的下一个结构体!也是处于安全考虑!
512 #define list_for_each_entry_safe_from(pos, n, head, member)             \ 513     for (n = list_entry(pos->member.next, typeof(*pos), member);        \ 514          &pos->member != (head);                        \ 515          pos = n, n = list_entry(n->member.next, typeof(*n), member))
和上面基本一样,只是上面是从pos所指的下一个结构体开始,这个是从pos所指的结构体开始!
527 #define list_for_each_entry_safe_reverse(pos, n, head, member)      \ 528     for (pos = list_entry((head)->prev, typeof(*pos), member),  \ 529         n = list_entry(pos->member.prev, typeof(*pos), member); \ 530          &pos->member != (head);                    \ 531          pos = n, n = list_entry(n->member.prev, typeof(*n), member))
和上一个基本相似,只是这个函数是向前开始遍历结构体,即从链的尾部开始遍历!
到此为止,list.h中的双向循环链表逐句分析结束!可能有很多错误的地方,希望大家指正! 在此和大家分享一下我的第一次看源码的心得吧,刚开始,记得是星期一的时候,陈老师刚说了这个事情,我就开始看了,说实话,我刚开始打开list.h有种头晕的感觉,那么多的代码,英文的注释!太恐怖了,但是当我看晚第一个结构体list_head的时候,我的兴趣上来了,它为什么这么定义呢?我往下看,不懂的地方不管,大概浏览一边,我就知道这个头文件是个定义了很多链表操作的头文件,那么第二遍,第三遍,第四遍.......慢慢的,一个一个函数我懂了,一边网上查,一边自己拿着笔画函数的链表,进行模拟操作,这样我们就可以清楚的知道,每一步的含义!就这样,list.h中的双向链表部分已经基本清楚了! 希望大家能一起分享,一起进步! 加油!

更多推荐

list.h头文件详解

本文发布于:2024-02-25 01:50:57,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1697390.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:详解   头文件   list

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!