首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 內核雙向循環鏈表深度解析(四):進階操作與遍歷宏全家桶

     編輯:關於C語言

前言

接上篇,本文講解鏈表判空、拼接、進階遍歷宏 (list_for_each_entry_* 系列) 及安全刪除遍歷,配合代碼示例與運行結果,助你徹底掌握內核鏈表精髓。

判空檢查

// 簡易判空:頭節點 next 指向自己
static inline int list_empty(const struct list_head *head) {
    return head->next == head;
}

// 嚴格判空:prev/next 均指向自己 (防止 prev 損壞)
static inline int list_empty_careful(const struct list_head *head) {
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}

鏈表拼接

底層 __list_splice

static 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 整條鏈表插入 head 之後。不檢查空鏈表,調用者自行保證。

封裝宏

// 拼接後丟棄 list 頭節點
static inline void list_splice(struct list_head *list, struct list_head *head) {
    if (!list_empty(list)) __list_splice(list, head);
}

// 拼接後重新初始化 list 頭節點 (變空鏈表)
static 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);
    }
}

示例:合併兩個學生鏈表

// ... 初始化 head1, head2 並各加 3 個學生 ...
list_splice(&head2, &head1); // head2 內容接到 head1 後,head2 被丟棄
// 遍歷 head1 可見 6 個學生
// 若用 list_splice_init,head2 重新變為可用的空鏈表頭

進階遍歷宏全家桶

宏用途關鍵差異
list_for_each_entry(pos, head, member)正向遍歷宿主結構體最常用,自動 list_entry
list_for_each_entry_reverse(pos, head, member)反向遍歷宿主結構體從 head->prev 開始
list_for_each_entry_continue(pos, head, member)從**已知 pos 的下一個**繼續遍歷不從頭開始,需 pos 有效
list_for_each_entry_continue_reverse(pos, head, member)從已知 pos 的上一個繼續反向遍歷同上反向版
list_for_each_entry_safe(pos, n, head, member)安全遍歷 (允許刪除 pos)預存下一個宿主指針到 n
list_for_each_entry_safe_reverse(pos, n, head, member)安全反向遍歷預存上一個宿主指針

關鍵宏展開解析

#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))
  • typeof(*pos):GCC 擴展,自動推導宿主結構體類型。
  • &pos->member != (head):比較的是**嵌入的 list_head 地址**與頭節點。
  • prefetch:CPU 預取優化,可忽略不影響邏輯。

安全遍歷示例:遍歷並刪除所有節點

struct list_head *pos, *n;
Student *s;
list_for_each_entry_safe(s, n, &head, list) {
    printf("del %s\n", s->name);
    list_del(&s->list); // 刪除當前節點
    free(s);            // 釋放宿主內存 (若動態分配)
}
// 循環結束後 head 恢復空鏈表狀態

輔助宏:list_prepare_entry

#define list_prepare_entry(pos, head, member) \
    ((pos) ? : list_entry(head, typeof(*pos), member))

用於 list_for_each_entry_continue:若 pos 為 NULL,則從頭節點構造一個虛擬 pos (實際指向頭節點),使後續 pos->member.next 正確指向第一個數據節點。

完整運行示例輸出

# 初始鏈表 (頭插 1,2,3)
student num: 3  name: Stu3
student num: 2  name: Stu2
student num: 1  name: Stu1

# list_splice 合併另一鏈表 (4,5,6) 後
student num: 6  name: Stu6
student num: 5  name: Stu5
student num: 4  name: Stu4
student num: 3  name: Stu3
student num: 2  name: Stu2
student num: 1  name: Stu1

# list_for_each_entry_safe 遍歷刪除
del Stu3
del Stu2
del Stu1
# 鏈表變空

總結與最佳實踐

  1. 優先用 list_for_each_entry 直接遍歷宿主,代碼最簡潔。
  2. 刪除時必須用 _safe 版本,否則 pos = pos->next 會訪問已釋放內存。
  3. 拼接大鏈表用 list_splice O(1),避免逐節點 list_move O(n)。
  4. 判空用 list_empty 即夠;併發場景下才需 list_empty_careful。
  5. 用戶態移植:提取宏定義,替換 typeof 為 C++ 模板或 __typeof__,去除內核專用屬性。

系列完結

至此,Linux 內核雙向循環鏈表核心機制、基礎/進階操作、遍歷宏全家桶已全部講解完畢。建議將 list.h 納入個人代碼庫,在任何 C/C++ 項目中復用這套經典、高效、類型安全的鏈表實現。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved