C語言面向對象編程

1.內核通用鏈表

一、什么是 list_head

list_head 是 Linux 內核中自己實現的一種 雙向循環鏈表 的結構,定義在 <linux/list.h> 中。它設計得非常輕巧、靈活,廣泛用于內核模塊、驅動、進程調度、網絡協議棧等。

它的關鍵思想是:

將鏈表結構嵌入到你的數據結構中,從而實現通用鏈表操作。


?二、結構定義
struct list_head {struct list_head *next, *prev;
};

每一個 list_head 實際就是兩個指針:指向下一個節點和上一個節點。


?三、如何使用

假設你有一個自定義結構體:

struct student {char name[20];int age;struct list_head list;  // 嵌入 list_head
};

這樣,每個 student 節點就能通過 list 成員串聯成一個鏈表。


四、常用操作宏(定義在 list.h)
宏 / 函數作用
LIST_HEAD(name)定義并初始化鏈表頭
INIT_LIST_HEAD(ptr)初始化某個節點(通常用于結構體內嵌)
list_add(new, head)添加到鏈表頭部
list_add_tail(new, head)添加到鏈表尾部
list_del(entry)刪除節點
list_empty(head)判斷鏈表是否為空
list_for_each(pos, head)遍歷鏈表(不獲取結構體)
list_for_each_entry(ptr, head, member)遍歷鏈表(獲取結構體指針)
list_for_each_entry_safe(...)安全遍歷(可刪除)


五、鏈表是循環的

鏈表頭的 next 指向第一個節點,prev 指向最后一個節點;尾節點的 next 又指向頭部。這是一種 循環雙向鏈表

七、完整例子

不依賴內核的用戶態實現(適合你直接編譯)

用戶空間簡化版 list.h

先把這段保存為 list.h

#ifndef _USER_LIST_H
#define _USER_LIST_H#include <stddef.h>struct list_head {struct list_head *next, *prev;
};#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)static inline void INIT_LIST_HEAD(struct list_head *list)
{list->next = list;list->prev = list;
}static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
{next->prev = new;new->next = next;new->prev = prev;prev->next = new;
}static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head->next);
}static inline void list_add_tail(struct list_head *new, struct list_head *head)
{__list_add(new, head->prev, head);
}static inline void __list_del(struct list_head * prev, struct list_head * next)
{next->prev = prev;prev->next = next;
}static inline void list_del(struct list_head *entry)
{__list_del(entry->prev, entry->next);entry->next = entry->prev = NULL;
}static inline int list_empty(const struct list_head *head)
{return head->next == head;
}#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)#define container_of(ptr, type, member) ({                      \const typeof( ((type *)0)->member ) *__mptr = (ptr);        \(type *)( (char *)__mptr - offsetof(type, member) );})#define list_entry(ptr, type, member) \container_of(ptr, type, member)#define list_for_each(pos, head) \for (pos = (head)->next; pos != (head); pos = pos->next)#define list_for_each_entry(pos, head, member)                          \for (pos = list_entry((head)->next, typeof(*pos), member);         \&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))#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"struct student {char name[20];int age;struct list_head list;
};LIST_HEAD(student_list);void add_student(const char *name, int age) {struct student *stu = malloc(sizeof(*stu));strcpy(stu->name, name);stu->age = age;INIT_LIST_HEAD(&stu->list);list_add_tail(&stu->list, &student_list);
}void show_students() {struct student *stu;list_for_each_entry(stu, &student_list, list) {printf("Name: %s, Age: %d\n", stu->name, stu->age);}
}void cleanup() {struct student *stu, *tmp;list_for_each_entry_safe(stu, tmp, &student_list, list) {list_del(&stu->list);free(stu);}
}int main() {add_student("Tom", 18);add_student("Jerry", 19);add_student("Alice", 20);printf("Student list:\n");show_students();cleanup();return 0;
}

分別介紹.h中的幾個宏定義:

①?offsetof宏

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

是 C 語言中非常經典的技巧,用來 計算結構體中某個成員變量相對于結構體起始地址的偏移量(以字節為單位)

假設你有一個結構體:

struct student {int id;             // 偏移 0char name[20];      // 偏移 4double gpa;         // 偏移 24(視平臺對齊規則)
};

現在你想知道 gpa 在結構體中的偏移是多少,可以這樣寫:

size_t offset = offsetof(struct student, gpa);

宏展開:

((size_t) &((struct student *)0)->gpa)

拆解:

  1. (struct student *)0:構造一個指向地址 0 的結構體指針(不會真的訪問地址 0,只是為了類型推導)。

  2. ((struct student *)0)->gpa:假裝在地址 0 上有一個結構體,訪問其成員 gpa,結果是一個 地址偏移表達式(例如 0 + 24,實際上就是 24 字節偏移)。

  3. &((struct student *)0)->gpa:取這個成員的地址(是個偏移地址,不是真地址)。

  4. (size_t):強制轉換為整數類型,得到偏移量。

例子

#include <stdio.h>
#include <stddef.h>struct my_struct {char a;         // offset 0int b;          // offset 4 (由于對齊)double c;       // offset 8
};int main() {printf("offset of a = %zu\n", offsetof(struct my_struct, a)); // 0printf("offset of b = %zu\n", offsetof(struct my_struct, b)); // 4printf("offset of c = %zu\n", offsetof(struct my_struct, c)); // 8
}

offsetof(TYPE, MEMBER) 返回成員 MEMBER 在結構體 TYPE 中的字節偏移量,用于高級結構體操作的基礎工具之一。?

  • offsetof 是由 C 標準定義的宏(在 <stddef.h> 中)。

  • 它是完全 編譯期計算的,不訪問內存,也不會有運行時開銷

  • 在結構體操作、容器設計、內核鏈表、序列化、手動內存布局等場景中非常有用。

②?container_of 宏:

#define container_of(ptr, type, member) ({                      \const typeof( ((type *)0)->member ) *__mptr = (ptr);        \(type *)( (char *)__mptr - offsetof(type, member) );})

從結構體的某個成員指針(ptr),反推出這個成員所屬的整個結構體的地址。

struct student {int id;struct list_head list;
};struct list_head *p = &stu->list;
struct student *stu_ptr = container_of(p, struct student, list);

這個宏干的事情就是:你只知道結構體中某個成員變量的地址 ptr,你想獲得這個變量所在的整個結構體 type *,它會幫你算出來。

  • offsetof(type, member):得到成員在結構體中的偏移。

  • (char *)__mptr - offset:從成員地址減去偏移就得到結構體地址。

③?list_entry

#define list_entry(ptr, type, member) \container_of(ptr, type, member)

通過鏈表指針 ptr 得到包含它的結構體 type *

這個宏就是對 container_of 的封裝,用在鏈表中,ptrstruct list_head * 類型。

④?list_for_each_entry

#define list_for_each_entry(pos, head, member)                          \for (pos = list_entry((head)->next, typeof(*pos), member);         \&pos->member != (head);                                       \pos = list_entry(pos->member.next, typeof(*pos), member))

遍歷鏈表中的每一個結構體元素(不是鏈表節點 list_head,而是包含它的那個結構體)。

  • pos:用于遍歷的結構體指針。

  • head:鏈表頭(struct list_head *)。

  • member:結構體中的鏈表成員名字。

⑤ list_for_each_entry_safe宏

#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))

是 Linux 內核鏈表中非常常用的一個 “安全遍歷”宏,用于在 遍歷過程中可能會刪除當前節點 的情況。它是對 list_for_each_entry 的增強版本。

對比來看:

list_for_each_entry

#define list_for_each_entry(pos, head, member)                          \for (pos = list_entry((head)->next, typeof(*pos), member);         \&pos->member != (head);                                       \pos = list_entry(pos->member.next, typeof(*pos), member))

這個宏用于普通的鏈表遍歷,pos 是當前元素指針。

但是 不能在遍歷中刪除當前元素,因為 pos = pos->next 是在遍歷尾部執行的,一旦 pos 被刪除了,就找不到下一個節點了,會導致訪問野指針。

struct my_struct *pos;
list_for_each_entry(pos, &head, list) {if (should_delete(pos)) {list_del(&pos->list);  // ?危險:pos下一次訪問就無效了kfree(pos);}
}

安全版本 list_for_each_entry_safe

#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))

多了一個變量 n(next):

  • 在遍歷當前節點時,預先保存好下一個節點 n

  • 即使你在中間 free(pos)list_del(&pos->member)也不會影響后續訪問 n,避免野指針。

struct my_struct *pos, *n;
list_for_each_entry_safe(pos, n, &head, list) {if (should_delete(pos)) {list_del(&pos->list);  // ?可以安全刪除kfree(pos);}
}

宏名作用
container_of從成員指針推導出結構體指針
list_entrylist_head* 拿到結構體指針
list_for_each_entry遍歷鏈表中包含 list_head 的結構體元素
list_for_each_entry_safe安全遍歷鏈表中包含 list_head 的結構體元素

為什么要用用戶空間簡化版不依賴內核的用戶態實現?

1. 用戶態無需加載內核模塊,更安全方便

  • 在用戶態編寫和運行程序不需要 root 權限,也不涉及內核模塊的編譯、加載、卸載。

  • 避免由于錯誤操作導致內核崩潰(例如 oops、panic)。

2. 無需依賴 Linux 內核頭文件

  • 內核中的 list_head 定義在 <linux/list.h>,不能直接在用戶空間中使用,因為它依賴于很多內核結構(如 container_ofprefetch、內存屏障等)。

  • 簡化版中只保留核心邏輯,移除內核復雜依賴,更適合教學和用戶態程序調試。

3. 易于學習和調試

  • 用戶態程序可以用 gdbprintf 等方式方便地調試,學習 list_head 的插入、刪除、遍歷操作。

  • 可以專注理解鏈表邏輯,而不必關心底層硬件資源或內核調度等復雜因素。

4. 跨平臺兼容性更好

  • 簡化版不依賴內核,在不同平臺和系統(如 macOS、Windows 下的 WSL)中也能運行和調試。

5. 利于單元測試或快速原型

  • 在開發內核模塊之前,可以在用戶空間用簡化版 list_head 提前驗證算法邏輯。

用戶空間簡化版特點:

  • 和內核的一樣,提供雙向循環鏈表的基本結構。

  • list_addlist_dellist_for_each 等函數模擬內核鏈表操作。

  • 通常會配套實現一個 container_of 宏,簡化結構體成員查找。

八、內核鏈表與普通鏈表的區別

從編寫代碼的角度看:

內核鏈表:

嵌入在結構體內部,相當于在結構體內部的鏈表

struct student {char name[20];int age;struct list_head list;
};

普通鏈表:

創建一個專門的鏈表節點,在鏈表內部的結構體

typedef struct ListNode {int data;               // 數據域(可根據需求修改類型)struct ListNode *next;  // 指針域,指向下一個節點
} ListNode;

特性優勢
通用性可嵌入任何結構體,實現任意鏈表類型。
內核標準幾乎所有內核鏈表都是用它做的
特性list_head(內核鏈表)普通鏈表(如學生手寫的)
?支持雙向鏈表默認支持需自己實現
節點結構通用通用結構體嵌入每種鏈表都得重新設計
插入/刪除效率高O(1),不需要額外判斷需特判頭/尾/空鏈表等情況
安全性高通過宏屏蔽指針錯誤易出現野指針、空指針
支持遍歷宏list_for_each 等宏方便遍歷手寫 while (node) 不易維護
支持從節點找回宿主結構體container_of() 宏可找回宿主結構體需要自己手動維護映射關系
易于模塊化封裝一套鏈表機制通用于所有模塊每個模塊都得造輪子

1. 通用性:

  • 你可以把 list_head 內嵌到任何結構體中,只要結構體里有 struct list_head 成員,就能掛進鏈表。

  • 所以它特別適合模塊化/插件化開發,廣泛用于內核、驅動、隊列系統等。


2. 性能:

  • 插入和刪除操作 不需要遍歷鏈表,也不需要特判頭尾節點,始終是 O(1)

  • 它用的是雙向循環鏈表結構(不是 NULL 結尾,而是指向自身),這讓很多操作邏輯更簡潔。


3. 安全性:

  • 它會自動維護前后指針,避免你手動操作 next/prev 出錯。

  • Linux 內核中還引入了調試輔助機制,能檢測非法訪問、雙重刪除等行為。


4. 反向查找能力:

  • 通過 list_entry()container_of() 宏,可以輕松從 list_head 節點獲取其宿主結構體的地址,實現靈活的對象管理。


?普通鏈表存在的問題

  • 每次寫都要重新定義結構體。

  • 插入/刪除時容易出現各種 bug。

  • 遍歷復雜,容易出現內存泄漏或懸掛指針。

  • 不支持從節點反查宿主結構體,缺乏靈活性。

  • 沒法通用于多個模塊,需要復制粘貼邏輯。

list_head 是一個“通用、可嵌入、性能高、安全”的鏈表實現,它解決的不是“如何實現鏈表”,而是“如何設計一套所有模塊都能復用、零成本維護的鏈表管理機制”。

你可以把它理解為 鏈表機制中的“操作系統級標準庫”,比自己寫一個鏈表強得多。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/90063.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/90063.shtml
英文地址,請注明出處:http://en.pswp.cn/web/90063.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Boot+Redis Zset:三步構建高可靠延遲隊列系統

系統設計架構圖---------------- ----------------- ---------------- | | | | | | | 生產者 |------>| Redis ZSet |------>| 定時任務消費者 | | (添加延遲任務) | | (延…

MCP vs 傳統集成方案:REST API、GraphQL、gRPC的終極對比

MCP vs 傳統集成方案&#xff1a;REST API、GraphQL、gRPC的終極對比 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;每一個特…

SQL語句中鎖的使用與優化

一、鎖機制簡介1.定義在數據庫中&#xff0c;除了傳統的計算資源&#xff08;如CPU、RAM、I/O等&#xff09;的爭用以外&#xff0c;數據也是一種供需要用戶共享的資源。如何保證數據并發訪問的一致性、有效性是所有數據庫必須解決的一個問題&#xff0c;鎖沖突也是影響數據庫并…

Linux筆記1——簡介安裝

操作系統給用戶一個操作界面&#xff0c;用戶通過操作界面使用系統資源Linux內核管理控制硬件&#xff0c;和硬件打交道SCSI&#xff08;盤&#xff09;sd**;第一個*表示磁盤順序&#xff0c;第二個*表示分區。例如&#xff1a;sda\sdb\sdc,sda1,sda2NVMe&#xff08;盤&#x…

GoLand 部署第一個項目

前言&#xff1a;Go環境部署分為兩種模式&#xff0c;一種是基于GOPATH部署&#xff08;老版本&#xff09;&#xff0c;另一種是基于Module部署&#xff08;新版本v1.11開始&#xff09;。GOPATH&#xff1a;需要配置GOPATH路徑&#xff0c;將GOPATH目錄視為工作目錄&#xff…

Mosaic數據增強介紹

1. 核心概念與目標Mosaic 是一種在計算機視覺&#xff08;尤其是目標檢測任務&#xff09;中非常流行且強大的數據增強技術。它最早由 Ultralytics 的 Alexey Bochkovskiy 在 YOLOv4 中提出并推廣&#xff0c;后來被廣泛應用于 YOLOv5, YOLOv7, YOLOv8 等模型以及其他目標檢測框…

LINUX 722 邏輯卷快照

邏輯卷快照 lvcreate -L 128M -s -n lv1-snap /dev/vg1/lv1 lvs lvscan mount -o ro /dev/vg1/lv1 /mmt/lv1-snap dmsetup ls --tree 測試 lvs /dev/vg1/lv1-snap dd if/dev/zero of/uc1/test bs1M count40 lvs /dev/vg1/lv1-snap 問題 [rootweb ~]# cd /mnt [rootweb mnt]# m…

Springboot+vue個人健康管理系統的設計與實現

文章目錄前言詳細視頻演示具體實現截圖后端框架SpringBoot前端框架Vue持久層框架MyBaits成功系統案例&#xff1a;代碼參考數據庫源碼獲取前言 博主介紹:CSDN特邀作者、985高校計算機專業畢業、現任某互聯網大廠高級全棧開發工程師、Gitee/掘金/華為云/阿里云/GitHub等平臺持續…

數據結構 --棧和隊鏈

一.棧的概念一種特殊的線性表&#xff0c;只能從固定的一端插入和刪除元素。棧中元素遵循先進后出的原則。二.模擬實現public class MyStack {public int size;public int[] array;public MyStack(){array new int[10];}private void grow(){array Arrays.copyOf(array,array…

文檔處理控件TX Text Control系列教程:使用 C# .NET 將二維碼添加到 PDF 文檔

PDF 文檔通常是合同、發票、證書和報告的最終格式。盡管它們在設計上是靜態的&#xff0c;但用戶現在希望能夠與它們交互、驗證信息并直接從這些文件訪問數字服務。這時&#xff0c;二維碼就變得至關重要。 PDF 文檔中的二維碼將印刷或數字內容與動態在線體驗連接起來。用戶只需…

Google Chrome 谷歌瀏覽器全部版本集合

Google Chrome 谷歌瀏覽器全部版本集合 Collection of all software versions of Google Chrome. 項目介紹 本項目為Google Chrome谷歌瀏覽器的全部版本集合&#xff0c;方便大家下載舊版本使用。 因為Gitee項目限制倉庫1G大小&#xff0c;所以許多谷歌瀏覽器版本無法上傳。…

論文略讀:Towards Safer Large Language Models through Machine Unlearning

ACL 2024大型語言模型&#xff08;LLMs&#xff09;的迅猛發展展現了其在多個領域的巨大潛力&#xff0c;這主要得益于其廣泛的預訓練知識和出色的泛化能力。然而&#xff0c;當面對問題性提示&#xff08;problematic prompts&#xff09;時&#xff0c;LLMs 仍然容易生成有害…

深度學習 ---參數初始化以及損失函數

深度學習 —參數初始化以及損失函數 文章目錄深度學習 ---參數初始化以及損失函數一&#xff0c;參數初始化1.1 固定值初始化1.1.1 全0初始化1.1.2 全1初始化1.3 任意常數初始化1.2 隨機初始化一&#xff0c;參數初始化 神經網絡的參數初始化是訓練深度學習模型的關鍵步驟之一…

JS--M端事件

移動端&#xff08;Mobile 端&#xff0c;簡稱 M 端&#xff09;開發中&#xff0c;由于設備特性&#xff08;觸摸屏、手勢操作等&#xff09;&#xff0c;需要處理一些與桌面端不同的事件。這些事件主要針對觸摸交互、手勢識別等場景 一、觸摸事件&#xff08;Touch Events&am…

Linux網絡編程-tcp

tcp、udp對比&#xff1a;UDP1. 特點無連接&#xff1a;無需建立連接即可發送數據。不可靠&#xff1a;不保證數據順序或完整性。低延遲&#xff1a;適合實時性要求高的場景。2. 應用場景視頻/音頻流傳輸&#xff08;如直播&#xff09;。DNS 查詢、在線游戲。TCP1. 特點面向連…

記一次flink資源使用優化

一.現狀分析 現有任務的資源配置如下&#xff0c;根據ui監控中Garbage Collection可以發現&#xff0c;此任務頻繁的發生GC&#xff0c;且老年代GC時間較久二.整體memory使用分析如下Framework Heap&#xff08;框架堆內存&#xff09;用于Flink框架自身的堆內存&#xff08;如…

Vue底層換成啥了?如何更新DOM的?

摘要&#xff1a;之前的vue是使用虛擬 DOM的&#xff0c;但是Vue 3.6 帶來了一個意義重大的更新&#xff1a; Vapor Mode 渲染模式。Vue 渲染策略的演進&#xff1a; Vue 1.x&#xff1a; 基于模板渲染策略&#xff0c;直接將模板轉換為DOM元素&#xff0c;并為每個DOM元素創建…

0722 數據結構順序表

Part 1.順序表的代碼一.順序表的內存申請head.h: typedef int datatype;typedef struct sqlist {//數據元素datatype data[MAXSIZE];//順序表長度int len;}*sqlist; //*sqlist的作用: //sqlist:struct Sqlist * sqlist create();head.c: sqlist create() {sqlist list (sqlist)…

為何在 Vue 的 v-model 指令中不能使用可選鏈(Optional Chaining)?

Vue 的 v-model 是實現組件與數據雙向綁定的核心指令之一&#xff0c;它本質上是一個語法糖&#xff0c;用于簡化對表單元素和組件 props 的同步更新。然而&#xff0c;在 Vue 3&#xff08;以及 Vue 2 的某些模式下&#xff09;&#xff0c;開發者嘗試在 v-model 中使用 JavaS…

基于單片機智能藥盒/智能藥箱/定時吃藥系統

傳送門 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目速選一覽表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目功能速覽 概述 本設計實現了一種基于單片機的智能藥盒&#xff0c;系統以微控制器&#xff08;如STM32&#xff…