Linux内核宏container_of

本文详细解析了container_of宏的工作原理,它通过编译器技巧计算结构体中成员变量的地址,展示了typeof、offsetof和类型转换在实现过程中的作用。通过实例说明了如何使用这些技术获取结构体实例的地址,适用于需要根据成员变量找到整个结构体的场景。
摘要由CSDN通过智能技术生成

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

2. typeof

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

typeof

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

3. (((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;
}

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

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

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

5. 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地址的一个偏移值。

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

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

7. 举例

#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,math);
    printf("sptr=%p\n",sptr);
    return 0; 
}

  运行结果如下:

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

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

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; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值