《電子技術應用》
您所在的位置:首頁 > 嵌入式技術 > 業界動態 > C語言的那些小秘密之鏈表

C語言的那些小秘密之鏈表

2015-05-29

  大多數的讀者在學習編程語言的時候都不喜歡那些枯燥的文字描述,包括我自己在開始學習編程的時候也是這樣,對于代碼的熱情遠遠高于文字,所以我在我寫東西的時候也不喜歡用枯燥的文字描述來向讀者講解,更喜歡用代碼加上適當的文字描述的方式進行講解,因為有些東西可能用枯燥的文字描述半天還不如實實在在的給讀者呈現出一段簡單的代碼,讓讀者理解得更加的透徹些。但是并不是說文字描述就沒用,文字描述也很重要,只是絕大部分讀者都更加的希望直接達到最終的效果,都想跳過那些中間的步驟。接下來我們接著上一篇博客《C語言的那些小秘密之鏈表(三)》的內容繼續講解linux內核雙向循環鏈表。[cpp] view plaincopystatic inline int list_empty(const struct list_head *head)

  {

  return head->next == head;

  }

  static inline int list_empty_careful(const struct list_head *head)

  {

  struct list_head *next = head->next;

  return (next == head) && (next == head->prev);

  }

 

  運行結果為:

  [html] view plaincopyroot@ubuntu:/home/paixu/dlist_node# ./a

  student num: 5 student name: Stu5

  student num: 4 student name: Stu4

  student num: 3 student name: Stu3

  student num: 2 student name: Stu2

  student num: 1 student name: Stu1

  使用list_empty()檢測,鏈表非空

  使用list_empty_careful()檢測,鏈表非空

  看看代碼就知道如何使用了,接下來看看鏈表的合成。

  [html] view plaincopystatic inline void __list_splice(struct list_head *list,

  struct list_head *head)

  {

  struct list_head *first = list->next;

  struct list_head *last = list->prev;

  struct list_head *at = head->next;

  first->prev = head;

  head->next = first;

  last->next = at;

  at->prev = last;

  }

  

  在這種情況下會丟棄list所指向的頭結點,因為兩個鏈表有兩個頭結點,所以我們必須要去掉其中一個頭結點。只要list非空鏈,head無任何限制,該函數就能實現鏈表的合并。

  [cpp] view plaincopystatic inline void list_splice_init(struct list_head *list,

  struct list_head *head)

  {

  if (!list_empty(list)) {

  __list_splice(list, head);

  INIT_LIST_HEAD(list);

  }

  }

  以上函數的功能是將一個鏈表list的有效信息合并到另外一個鏈表head后,重新初始化被去掉的空的鏈表頭。這樣的描述可能不是太好理解,接下來看看一段代碼。

  [html] view plaincopy#include

  #include

  #include "list.h"

  typedef struct _stu

  {

  char name[20];

  int num;

  struct list_head list;

  }stu;

  int main()

  {

  stu *pstu,*pstu2;

  stu *tmp_stu;

  struct list_head stu_list,stu_list2;

  struct list_head *pos;

  int i = 0;

  INIT_LIST_HEAD(&stu_list);

  INIT_LIST_HEAD(&stu_list2);

  pstu = malloc(sizeof(stu)*3);

  pstu2 = malloc(sizeof(stu)*3);

  for(i=0;i<3;i++)

  {

  sprintf(pstu[i].name,"Stu%d",i+1);

  sprintf(pstu2[i].name,"Stu%d",i+4);

  pstu[i].num = i+1;

  pstu2[i].num = i+4;

  list_add( &(pstu[i].list), &stu_list);

  list_add( &(pstu2[i].list), &stu_list2);

  }

  printf("stu_list 鏈表\n");

  list_for_each(pos,&stu_list)

  {

  tmp_stu = list_entry(pos, stu, list);

  printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

  }

  printf("stu_list2 鏈表\n");

  list_for_each(pos,&stu_list2)

  {

  tmp_stu = list_entry(pos, stu, list);

  printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

  }

  printf("stu_list鏈表和stu_list2 鏈表合并以后\n");

  list_splice(&stu_list2,&stu_list);

  list_for_each(pos,&stu_list)

  {

  tmp_stu = list_entry(pos, stu, list);

  printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

  }

  free(pstu);

  return 0;

  }

  運行結果為:

  [html] view plaincopyroot@ubuntu:/home/paixu/dlist_node# ./a

  stu_list 鏈表

  student num: 3 student name: Stu3

  student num: 2 student name: Stu2

  student num: 1 student name: Stu1

  stu_list2 鏈表

  student num: 6 student name: Stu6

  student num: 5 student name: Stu5

  student num: 4 student name: Stu4

  stu_list鏈表和stu_list2 鏈表合并以后

  student num: 6 student name: Stu6

  student num: 5 student name: Stu5

  student num: 4 student name: Stu4

  student num: 3 student name: Stu3

  student num: 2 student name: Stu2

  student num: 1 student name: Stu1

  有了直觀的代碼和運行結果,理解起來也更加的容易了。

  有了上面的這些操作,但是我們還一直沒有講到我們最終所關心的宿主結構,那么接下來我們一起來看看我們該如何取出宿主結構的指針呢?這也是我認為linux內核雙向循環鏈表實現最為巧妙的地方了。

  [cpp] view plaincopy#define list_entry(ptr, type, member) \

  ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

  看看上面的代碼,發現一個很熟悉的身影(unsigned long)(&((type *)0)->member)),這個我在前一篇博客《C語言的那些小秘密之字節對齊》中已經講解過了,多以在此就不再做過多的講解,如果有不明白的讀者可以回過去看看講解再回過來閱讀。通過(unsigned long)(&((type *)0)->member))我們得出了成員變量member的偏移量,而ptr為指向member的指針,因為指針類型不同的原因,所以我們再次要先進行(char*)的轉換之后再進行計算。所以我們用ptr減去member的偏移量就得到了宿主結構體的指針,這就是一個非常巧妙的地方,這也就使得linux內核雙向循環鏈表能夠區別于傳統鏈表的關鍵所在。可能看到這兒的時候讀者已經感覺非常的枯燥了,但是別放棄,堅持看完,因為雖然這樣的講解枯燥了點,但是非常有用。所以堅持堅持吧!

  [cpp] view plaincopy#define list_for_each(pos, head) \

  for (pos = (head)->next; prefetch(pos->next), pos != (head); \

  pos = pos->next)

  #define __list_for_each(pos, head) \

  for (pos = (head)->next; pos != (head); pos = pos->next)

  #define list_for_each_prev(pos, head) \

  for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \

  pos = pos->prev)

  遍歷是雙循環鏈表的基本操作,head為頭節點,遍歷過程中首先從(head)->next開始,當pos==head時退出,故head節點并沒有訪問,這和鏈表的結構設計有關,通常頭節點都不含有其它有效信息,因此可以把頭節點作為雙向鏈表遍歷一遍的檢測標志來使用。在list_for_each宏中讀者可能發現一個比較陌生的面孔,我們在此就不將prefetch展開了講解了,有興趣的讀者可以自己查看下它的實現,其功能是預取內存的內容,也就是程序告訴CPU哪些內容可能馬上用到,CPU預先其取出內存操作數,然后將其送入高速緩存,用于優化,是的執行速度更快。接下來的__list_for_each()宏和list_for_each_prev()宏就不在此做一一的講解了,和list_for_each()宏類似。 就是遍歷的方向有所改變或者不使用預取。

  通過上面的講解和前面那么多的代碼,相信讀者這個時候對于list_for_each()已經不再感到陌生了。上面的但三個遍歷鏈表的宏都類似,繼續往下看。

  [cpp] view plaincopy#define list_for_each_safe(pos, n, head) \

  for (pos = (head)->next, n = pos->next; pos != (head); \

  pos = n, n = pos->next)

  以上list_for_each_safe()宏也同樣是用于遍歷的,不同的是里邊多出了一個n暫存pos下一個節點的地址,避免了因為pos節點被釋放而造成的斷鏈,這也就體現出了safe。也就是說你可以遍歷完當前節點后將其刪除,同時可以接著訪問下一個節點,遍歷完畢后就只剩下一個頭節點。當然這有一個最為典型的應用,那就是我們在多進程編程時候,多個進程等待在同一個等待隊列上,若事件發生時喚醒所有進程,則可以喚醒后將其依次從等待隊列中刪除。

  [html] view plaincopy#include

  #include

  #include "list.h"

  typedef struct _stu

  {

  char name[20];

  int num;

  struct list_head list;

  }stu;

  int main()

  {

  stu *pstu;

  stu *tmp_stu;

  struct list_head stu_list;

  struct list_head *pos,*n;

  int i = 0;

  INIT_LIST_HEAD(&stu_list);

  pstu = malloc(sizeof(stu)*3);

  for(i=0;i<3;i++)

  {

  sprintf(pstu[i].name,"Stu%d",i+1);

  pstu[i].num = i+1;

  list_add( &(pstu[i].list), &stu_list);

  }

  printf("通過list_for_each_safe()遍歷使用list_del(pos)刪除結點前\n");

  list_for_each_safe(pos, n, &stu_list)

  {

  tmp_stu = list_entry(pos, stu, list);

  printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

  list_del(pos);

  }

  printf("通過list_for_each()遍歷使用list_del(pos)刪除結點后\n");

  list_for_each(pos,&stu_list)

  {

  tmp_stu = list_entry(pos, stu, list);

  printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

  }

  free(pstu);

  return 0;

  }

  運行結果為:

  [html] view plaincopyroot@ubuntu:/home/paixu/dlist_node# ./a

  通過list_for_each_safe()遍歷使用list_del(pos)刪除結點前

  student num: 3 student name: Stu3

  student num: 2 student name: Stu2

  student num: 1 student name: Stu1

  通過list_for_each()遍歷使用list_del(pos)刪除結點后

  讀者可以結合運行結果再去閱讀上面的文字描述部分。

  如果只提供對list_head結構的遍歷操作是遠遠不夠的,我們希望實現的是對宿主結構的遍歷,即在遍歷時直接獲得當前鏈表節點所在的宿主結構項,而不是每次要同時調用list_for_each()和list_entry()。為此Linux特地提供了list_for_each_entry()宏來實現

  [cpp] view plaincopy#define list_for_each_entry(pos, head, member) \

  for (pos = list_entry((head)->next, typeof(*pos), member); \

  prefetch(pos->member.next), &pos->member != (head); \

  pos = list_entry(pos->member.next, typeof(*pos), member))

  第一個參數為傳入的遍歷指針,指向宿主數據結構,第二個參數為鏈表頭,為list_head結構,第三個參數為list_head結構在宿主結構中的成員名。有時候做過多的講解反而沒有看看代碼的效果好,我們還是用段代碼來說明下吧。

  [html] view plaincopy#include

  #include

  #include "list.h"

  typedef struct _stu

  {

  char name[20];

  int num;

  struct list_head list;

  }stu;

  int main()

  {

  stu *pstu;

  stu *tmp_stu;

  struct list_head stu_list;

  struct list_head *pos,*n;

  int i = 0;

  INIT_LIST_HEAD(&stu_list);

  pstu = malloc(sizeof(stu)*3);

  for(i=0;i<3;i++)

  {

  sprintf(pstu[i].name,"Stu%d",i+1);

  pstu[i].num = i+1;

  list_add( &(pstu[i].list), &stu_list);

  }

  list_for_each_entry(tmp_stu, &stu_list, list)

  printf("student num: %d\tstudent name: %s\n",tmp_stu->num,tmp_stu->name);

  free(pstu);

  return 0;

  }

  運行結果為:

  [html] view plaincopyroot@ubuntu:/home/paixu/dlist_node# ./a

  student num: 3 student name: Stu3

  student num: 2 student name: Stu2

  student num: 1 student name: Stu1

  如果讀者一開始對于文字描述感到陌生的話,那么就再次結合代碼去閱讀。

  接下來再來看看最后幾個。

  [html] view plaincopy#define list_for_each_entry_reverse(pos, head, member) \

  for (pos = list_entry((head)->prev, typeof(*pos), member); \

  prefetch(pos->member.prev), &pos->member != (head); \

  pos = list_entry(pos->member.prev, typeof(*pos), member))

  #define list_prepare_entry(pos, head, member) \

  ((pos) ? : list_entry(head, typeof(*pos), member))

  #define list_for_each_entry_continue(pos, head, member) \

  for (pos = list_entry(pos->member.next, typeof(*pos), member); \

  prefetch(pos->member.next), &pos->member != (head); \

  pos = list_entry(pos->member.next, typeof(*pos), member))

  #define list_for_each_entry_safe(pos, n, head, member) \

  for (pos = list_entry((head)->next, typeof(*pos), member), \

  n = list_entry(pos->member.next, typeof(*pos), member); \

  &pos->member != (head); \

  pos = n, n = list_entry(n->member.next, typeof(*n), member))

  以上幾個與list_for_each_entry類似,只是其中略有差別,list_prepare_entry()中含有prefetch(),它的作用在上面已經講解,有什么疑惑可以返回去閱讀下,在此不再做過多的講解;list_for_each_entry_continue()和list_for_each_entry()的區別主要是list_for_each_entry_continue()可以不從鏈表頭開始遍歷,而是從已知的某個pos結點的下一個結點開始遍歷。在某些時候如果不是從頭結點開始遍歷,那么為了保證pos的初始值有效,必須使用list_prepare_entry()。其含義就是如果pos非空,那么pos的值就為其本身,如果pos為空,那么就從鏈表頭強制擴展一個虛pos指針,讀者自己分析list_prepare_entry()的實現就明白了。list_for_each_entry_safe()要求調用者另外提供一個與pos同類型的指針n,在for循環中暫存pos下一個節點的宿主結構體的地址,避免因pos節點被釋放而造成的斷鏈。

  到此我們linux內核雙向循環鏈表的旅程就結束了,下一篇我們將開始一個新的旅程。由于本人水平有限,博客中的不妥或錯誤之處在所難免,殷切希望讀者批評指正。同時也歡迎讀者共同探討相關的內容,如果樂意交流的話請留下你寶貴的意見。


本站內容除特別聲明的原創文章之外,轉載內容只為傳遞更多信息,并不代表本網站贊同其觀點。轉載的所有的文章、圖片、音/視頻文件等資料的版權歸版權所有權人所有。本站采用的非本站原創文章及圖片等內容無法一一聯系確認版權者。如涉及作品內容、版權和其它問題,請及時通過電子郵件或電話通知我們,以便迅速采取適當措施,避免給雙方造成不必要的經濟損失。聯系電話:010-82306118;郵箱:aet@chinaaet.com。
亚洲一区二区欧美_亚洲丝袜一区_99re亚洲国产精品_日韩亚洲一区二区
中国成人亚色综合网站| 欧美有码在线视频| 亚洲午夜电影在线观看| 亚洲国产成人午夜在线一区| 国产亚洲欧美日韩精品| 国产精品腿扒开做爽爽爽挤奶网站| 欧美精品自拍偷拍动漫精品| 欧美bbbxxxxx| 麻豆av一区二区三区久久| 久久国内精品视频| 欧美影院成人| 欧美一区二区三区在线视频| 小嫩嫩精品导航| 香蕉久久夜色精品| 午夜视频久久久| 羞羞答答国产精品www一本| 亚洲女爱视频在线| 亚洲免费影视第一页| 亚洲免费人成在线视频观看| 亚洲欧美一区二区精品久久久| 亚洲欧美国产视频| 亚洲影院色无极综合| 亚洲一线二线三线久久久| 亚洲免费影视第一页| 欧美一级淫片播放口| 久久国产一区| 老司机免费视频久久| 免费日本视频一区| 欧美v国产在线一区二区三区| 欧美成人a∨高清免费观看| 欧美风情在线观看| 欧美日韩国产不卡在线看| 亚洲网站视频| 在线视频你懂得一区| 亚洲性图久久| 午夜精品剧场| 久久精品人人做人人综合| 亚洲日本中文字幕免费在线不卡| 亚洲精品综合| 亚洲永久精品国产| 久久成人精品无人区| 快播亚洲色图| 欧美日韩网址| 国产日韩欧美在线一区| 影音先锋亚洲电影| 亚洲国语精品自产拍在线观看| 99国产精品99久久久久久粉嫩| 亚洲综合首页| 亚洲激情视频在线| 亚洲视频免费在线| 欧美一区二区三区男人的天堂| 久久综合网hezyo| 欧美日韩成人在线播放| 国产精品久久久久久妇女6080| 国产亚洲成人一区| 亚洲国产精品第一区二区| 一区二区av在线| 欧美主播一区二区三区美女 久久精品人| 亚洲激情婷婷| 午夜精品久久久久久久99热浪潮| 久久一二三国产| 欧美色视频日本高清在线观看| 国产欧美一区二区三区在线老狼 | 亚洲高清视频在线观看| 一区二区三区视频免费在线观看| 久久xxxx精品视频| 一区二区三区视频观看| 久久超碰97中文字幕| 欧美激情一区二区三区不卡| 国产精品一区久久久久| 亚洲国产精品传媒在线观看| 亚洲欧美中文另类| 亚洲精品在线观| 久久av一区二区三区亚洲| 欧美激情视频一区二区三区不卡| 国产伦一区二区三区色一情| 亚洲经典视频在线观看| 欧美一区日韩一区| 一区二区91| 女人色偷偷aa久久天堂| 国产欧美日韩在线视频| 亚洲精品美女| 亚洲电影在线看| 午夜久久久久| 欧美日韩免费观看一区三区| 在线观看亚洲精品| 午夜精品久久久久久99热| 一区二区三区精品在线| 蜜桃av综合| 国产午夜精品福利| 日韩一级裸体免费视频| 亚洲人www| 久久精品最新地址| 国产精品久久久久毛片软件| 亚洲人成77777在线观看网| 久久精品五月| 久久国产精品第一页| 国产精品久久77777| 亚洲精品一级| 亚洲欧洲日本在线| 久久偷窥视频| 国产一区自拍视频| 午夜精品一区二区三区在线视 | 国产日韩欧美综合在线| 中国亚洲黄色| 99在线热播精品免费| 老司机亚洲精品| 国产一区二区三区久久久| 亚洲综合二区| 亚洲欧美日本视频在线观看| 欧美三级欧美一级| 亚洲毛片在线观看.| 日韩亚洲欧美高清| 欧美电影资源| 亚洲激情在线激情| 日韩午夜电影av| 欧美高清视频www夜色资源网| 一区二区在线观看av| 久久精品视频免费播放| 久久免费视频这里只有精品| 国产在线欧美| 亚洲国产精品久久久久秋霞不卡 | 国产欧美一区二区在线观看| 亚洲欧美激情视频在线观看一区二区三区 | 亚洲小说春色综合另类电影| 99成人精品| 欧美激情综合在线| 亚洲国产毛片完整版| 亚洲人成毛片在线播放女女| 欧美国产第一页| 亚洲日本欧美天堂| 一区二区三区欧美视频| 欧美福利一区二区| 亚洲人成人一区二区三区| 亚洲看片网站| 欧美精品免费观看二区| 99精品热视频| 亚洲欧美日韩在线综合| 国产乱码精品一区二区三区av| 亚洲欧美国产va在线影院| 久久国产精品久久国产精品| 好吊色欧美一区二区三区视频| 亚洲国产婷婷香蕉久久久久久99 | 国产日韩欧美二区| 性欧美videos另类喷潮| 久久亚洲美女| 亚洲激情视频在线播放| 一本久道久久综合中文字幕 | 亚洲精品国产精品国自产在线| 亚洲私人影吧| 国产欧美一区二区三区久久人妖| 久久av一区| 欧美激情精品久久久久久| 日韩图片一区| 亚洲欧美日韩电影| 国产婷婷97碰碰久久人人蜜臀| 亚洲福利视频一区| 欧美国产91| 亚洲一区二区三区777| 久久精品中文字幕一区二区三区 | 亚洲免费观看在线观看| 亚洲欧美制服中文字幕| 国产亚洲欧美色| 一本大道久久a久久精品综合| 国产精品青草综合久久久久99| 欧美专区日韩专区| 欧美精品粉嫩高潮一区二区| 亚洲视频视频在线| 久久综合成人精品亚洲另类欧美| 亚洲激情综合| 欧美一区在线直播| 亚洲国产精品久久久久秋霞影院| 亚洲图中文字幕| 国内精品伊人久久久久av影院 | 国产精品草草| 久久精品欧美日韩| 欧美日韩国产综合网| 午夜亚洲一区| 欧美日韩国产精品一区| 校园春色国产精品| 欧美激情亚洲自拍| 亚洲欧美日韩在线播放| 免费看精品久久片| 亚洲一区二区免费视频| 欧美fxxxxxx另类| 午夜精品久久久久久久久久久| 欧美激情一区在线| 欧美一级艳片视频免费观看| 欧美日本二区| 久久精品欧洲| 国产精品一区二区欧美| 亚洲精品日韩激情在线电影 | 国产一区二区激情| 99精品免费网| 红桃视频亚洲| 亚洲欧美在线免费| 亚洲精品日韩在线观看| 久久频这里精品99香蕉| 亚洲一区二区三区免费视频| 欧美美女bb生活片|