接上篇,本文講解鏈表判空、拼接、進階遍歷宏 (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_splicestatic 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
# 鏈表變空
list_for_each_entry 直接遍歷宿主,代碼最簡潔。_safe 版本,否則 pos = pos->next 會訪問已釋放內存。list_splice O(1),避免逐節點 list_move O(n)。list_empty 即夠;併發場景下才需 list_empty_careful。typeof 為 C++ 模板或 __typeof__,去除內核專用屬性。至此,Linux 內核雙向循環鏈表核心機制、基礎/進階操作、遍歷宏全家桶已全部講解完畢。建議將 list.h 納入個人代碼庫,在任何 C/C++ 項目中復用這套經典、高效、類型安全的鏈表實現。