前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核中container_of宏的详细解释

Linux内核中container_of宏的详细解释

作者头像
嵌入式与Linux那些事
发布2021-05-20 14:25:14
1.2K0
发布2021-05-20 14:25:14
举报

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

代码语言:javascript
复制
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;
}

  打印结果如下:

代码语言:javascript
复制
//结构体的地址
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的地址。

container_of宏

代码语言:javascript
复制
#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宏的作用是通过结构体内某个成员变量的地址和该变量名,以及结构体类型。找到该结构体变量的地址。这里使用的是一个利用编译器技术的小技巧,即先求得结构成员在结构中的偏移量,然后根据成员变量的地址反过来得出主结构变量的地址。下面具体分析下各个部分:

typeof

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

typeof

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

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

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

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

代码语言:javascript
复制
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;
}

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

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

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

offsetof(type, member))

代码语言:javascript
复制
((size_t) &((TYPE*)0)->MEMBER)

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

代码语言:javascript
复制
typedef unsigned int size_t;

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

代码语言:javascript
复制
typedef unsigned long size_t;

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

代码语言:javascript
复制
for(size_t i=0;i<300;i++)

  为了使程序有很好的移植性,因此内核使用size_t和,而不是int,unsigned。

((size_t) &((TYPE*)0)->MEMBER) 结合之前的解释,我们可以知道这句话的意思就是求出MEMBER相对于0地址的一个偏移值。

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

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

举例

代码语言:javascript
复制
#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,"feizhufeifei");
		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;	
}

  运行结果如下:

代码语言:javascript
复制
sptr=0xffffcb90
sptr=0xffffcb90
sptr=0xffffcbb4

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

代码语言:javascript
复制
int main()
{
  		Student stu;
        Student *sptr = NULL;
		stu.id = 123456;
		strcpy(stu.name,"feizhufeifei");
		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;	
}

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结构体在内存中是如何存储的
  • container_of宏
  • typeof
  • (((type *)0)->member)
  • const typeof(((type )0)->member)__mptr = (ptr);
  • offsetof(type, member))
  • (type *)((char *)__mptr - offsetof(type, member))
  • 举例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档