千家信息网

Linux内核宏Container_Of的案例分析

发表于:2025-11-13 作者:千家信息网编辑
千家信息网最后更新 2025年11月13日,这篇文章给大家介绍Linux内核宏Container_Of的案例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1. 结构体在内存中是如何存储的int main() { S
千家信息网最后更新 2025年11月13日Linux内核宏Container_Of的案例分析

这篇文章给大家介绍Linux内核宏Container_Of的案例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

1. 结构体在内存中是如何存储的

int main() {   Student stu;  stu.id = 123456;  strcpy(stu.name,"feizhufeifei");  stu.math = 90;  stu.PE = 80;  printf("Student:%p\r\n",&stu);  printf("stu.ID:%p\r\n",&stu.ID);  printf("stu.name:%p\r\n",&stu.name);  printf("stu.math:%p\r\n",&stu.math);  return 0; }

打印结果如下:

//结构体的地址 Student:0xffffcbb0 //结构体第一个成员的地址 stu.ID:0xffffcbb0  //偏移地址 +0 stu.name:0xffffcbb4//偏移地址 +4 stu.math:0xffffcbd4//偏移地址 +24

??我们可以看到,结构体的地址和结构体第一个成员的地址是相同的。这也就是我们之前在拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)中提到的为什么在结构体中要把 struct list_head放在首位。

不太理解的再看下这两个例子:

  • struct A { int a; char b; int c; char d; };a 偏移为 0 , b 偏移为 4 , c 偏移为 8 (大于 4 + 1 的 4 的最小整数倍), d 偏移为 12 。A 对齐为 4 ,大小为 16 。

  • struct B { int a; char b; char c; long d; };a 偏移为 0 , b 偏移为 4 , c 偏移为 5 , d 偏移为 8 。B 对齐为 8 , 大小为 16 。

我们可以看到,结构体中成员变量在内存中存储的其实是偏移地址。也就是说结构体A的地址+成员变量的偏移地址 = 结构体成员变量的起始地址。

因此,我们也可以根据结构体变量的起始地址和成员变量的偏移地址来反推出结构体A的地址。

2. container_of宏

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

??首先看下三个参数, ptr是成员变量的指针, type是指结构体的类型, member是成员变量的名字。

??container_of宏的作用是通过结构体内某个成员变量的地址和该变量名,以及结构体类型,找到该结构体变量的地址。这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在结构中的偏移量,然后根据成员变量的地址反过来得出主结构变量的地址。下面具体分析下各个部分。

3. typeof

首先看下typeof,是用于返回一个变量的类型,这是GCC编译器的一个扩展功能,也就是说typeof是编译器相关的。既不是C语言规范的所要求,也不是某个标准的一部分。

int main() {  int a = 5;  //这里定义一个和a类型相同的变量b  typeof(a) b  = 6;  printf("%d,%d\r\n",a,b);//5 6  return 0; }

4. (((type *)0)->member)

((TYPE *)0) 将0转换为type类型的结构体指针,换句话说就是让编译器认为这个结构体是开始于程序段起始位置0,开始于0地址的话,我们得到的成员变量的地址就直接等于成员变量的偏移地址了。

(((type *)0)->member) 引用结构体中MEMBER成员。

typedef struct student{  int id;  char name[30];  int math; }Student; int main() {  //这里时把结构体强制转换成0地址,然后打印name的地址。  printf("%d\r\n",&((Student *)0)->name);//4  return 0; }

5. const typeof(((type * )0) ->member)*__mptr = (ptr);

这句代码意思是用typeof()获取结构体里member成员属性的类型,然后定义一个该类型的临时指针变量__mptr,并将ptr所指向的member的地址赋给__mptr;

为什么不直接使用 ptr 而要多此一举呢?我想可能是为了避免对 ptrprt 指向的内容造成破坏。

6. offsetof(type, member))

((size_t) &((TYPE*)0)->MEMBER)

size_t是标准C库中定义的,在32位架构中被普遍定义为:

typedef unsigned int size_t;

而在64位架构中被定义为:

typedef unsigned long size_t;

可以从定义中看到,size_t是一个非负数,所以size_t通常用来计数(因为计数不需要负数区):

for(size_t i=0;i<300;i++)

为了使程序有很好的移植性,因此内核使用size_t,而不是int,unsigned。((size_t) &((TYPE*)0)->MEMBER) 结合之前的解释,我们可以知道这句话的意思就是求出MEMBER相对于0地址的一个偏移值。

7. (type * )((char * )__mptr - offsetof(type, member))

这句话的意思就是,把 __mptr 转换成 char* 类型。因为 offsetof 得到的偏移量是以字节为单位。两者相减得到结构体的起始位置, 再强制转换成 type 类型。

8. 举例

#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) );})          typedef struct student {  int id;  char name[30];  int math; }Student;  int main() {     Student stu;         Student *sptr = NULL;   stu.id = 123456;   strcpy(stu.name,"zhongyi");   stu.math = 90;         sptr = container_of(&stu.id,Student,id);         printf("sptr=%p\n",sptr);         sptr = container_of(&stu.name,Student,name);         printf("sptr=%p\n",sptr);         sptr = container_of(&stu.math,Student,id);         printf("sptr=%p\n",sptr);         return 0;  }

运行结果如下:

sptr=0xffffcb90 sptr=0xffffcb90 sptr=0xffffcbb4

宏展开可能会看的更清楚一些

int main() {     Student stu;         Student *sptr = NULL;   stu.id = 123456;   strcpy(stu.name,"zhongyi");   stu.math = 90;   //展开替换         sptr = ({ const unsigned char  *__mptr = (&stu.id); (Student *)( (char *)__mptr - ((size_t) &((Student *)0)->id) );});         printf("sptr=%p\n",sptr);         //展开替换         sptr = ({ const unsigned char  *__mptr = (&stu.name); (Student *)( (char *)__mptr - ((size_t) &((Student *)0)->name) );});         printf("sptr=%p\n",sptr);         //展开替换         sptr = ({ const unsigned int *__mptr = (&stu.math); (Student *)( (char *)__mptr - ((size_t) &((Student *)0)->math) );});         printf("sptr=%p\n",sptr);         return 0;  }

关于Linux内核宏Container_Of的案例分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

地址 结构 偏移 变量 成员 类型 内核 编译器 编译 起始 分析 也就是 内容 就是 意思 指针 案例 案例分析 相同 也就是说 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 一站式网络技术优化 mysql数据库开启关闭命令 tcp服务器代码基本原理 在服务器上如何查看代理是否成功 什么是兼职app软件开发 奥迪导航数据库不可用 网络安全宣讲会海报 水果分析如果建立数据库 信息安全与网络安全的相同之处 java软件开发项目借鉴 贵阳国家网络安全会议 2021年度网络安全工作方案 美图手机无服务器 数据库技术论文3000字 网络安全及防诈骗宣讲材料 杨浦区创新软件开发厂家售后保障 闵行区机械网络技术哪家强 许昌软件开发网上价格 汽车自动化网络技术 数据库连接到服务器 湖南网络安全审计硬件厂家 网络安全手抄报字较少的 青浦区大规模软件开发服务密度 5g网络安全概念股 人社网络安全应急演练 车载网络技术凌永成免费下载 datalake数据库选型 易合诚网络技术服务部 河南省炽天使网络技术游戏 数据库之间存储的是什么
0