container_of

//获取结构体成员相对于结构体的偏移 
#define offsetof(TYPE,MEMBER) ((int) &((TYPE *)0)->MEMBER)  
//通过获取结构体中的某个成员,反推该结构体的指针 
#define container_of(ptr, type , member) ({ \
    const typeof(((type *)0)->member) *__mptr = (ptr) ; \
    (type *)((char *)__mptr - offsetof(type,member)) ;})
 

工作原理:

先用typeof获取变量的数据类型,也就是member成员的类型,然后将member这个成员 的指针转成自己类型的指针,再从offsetof相减,就得到整个结构体变量的首地址了,再将该地址强制转化为type *。

1、container_of宏第一步是做类型检查的,也就是检查ptr是否是指向结构成员member的,如果我们用typeof求出来的类型和ptr不一致,那么编译器会报错。为啥要做这个检查呢?因为ptr和member都是人工输入的参数,宏要保证它们是结构体成员和其相关联的指针,这个宏才有意义,所以类型检查是必须的。

      2、第二步相减时,把mptr指针强转成(char *)是因为,char指针减法只移一个字节,如果这样才可能得出准确的地址,否则,改为int类型,在减1就移动4个就乱了。

我们定义一个变量的格式是:修饰符+变量类型+变量名 = 右值;
修饰符            变量类型                                变量名     右值
const     typeof( ((type*)0)->member )    *__mptr =  (ptr) ;

现在看明白了吗,抛开具体细节,“typeof( ((type*)0)->member )”代表的是一种数据类型,那么它是什么样的数据类型呢?
((type*)0):它把0转换为一个type类型(也就是宿主结构体类型),为什么要这样做,且看后文
((type*)0)->member:这个0指针指向结构体中的member成员
typeof是gcc的c语言扩展保留字,用于获取变量的类型
 typeof( ((type*)0)->member )    *:得出member的数据类型

所以,第2行的结果就是定义一个指向member的指针,并赋值为ptr
 

 

在linux 内核编程中,会经常见到一个宏函数container_of(ptr,type,member), 但是当你通过追踪源码时,像我们这样的一般人就会绝望了(这一堆都是什么呀? 函数还可以这样定义??? 怎么还有0呢???  哎,算了,还是放弃吧。。。)。 这就是内核大佬们厉害的地方,随便两行代码就让我们怀疑人生,凡是都需要一个过程,慢慢来吧。

        其实,原理很简单:  已知结构体type的成员member的地址ptr,求解结构体type的起始地址。

                  type的起始地址 = ptr - size      (这里需要都转换为char *,因为它为单位字节)。

       到此,该函数已经讲完,是不是很简单??? 其实也不是,这里并没有提到size如何计算,而令我们头晕的正是这里。

    好吧,先上container of函数原型:

#define container_of(ptr, type, member) ({              \         
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \         
(type *)( (char *)__mptr - offsetof(type,member) );})
    其次为 offserof 函数原型:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  怎么样,是不是很炫?  好吧,下面开始揭开面纱:

  (一)0 指针的使用    (自己给的名字,不知有木问题)

            让事实说话:

#include<stdio.h>
 
struct test
{
    char i ;
    int j;
    char k;
};
 
int main()
{
    struct test temp;
    printf("&temp = %p\n",&temp);   
    printf("&temp.k = %p\n",&temp.k);
    printf("&((struct test *)0)->k = %d\n",((int)&((struct test *)0)->k));
 
}
 编译运行,可以得到如下结果:

&temp = 0xbf9815b4
&temp.k = 0xbf9815bc
&((struct test *)0)->k = 8
 什么意思看到了吧,自定义的结构体有三个变量:i,j,k。 因为有字节对齐要求,所以该结构体大小为4bytes * 3 =12 bytes.   而&((struct test *)0)->k 的作用就是求 k到结构体temp起始地址的字节数大小(就是我们的size)。在这里0被强制转化为struct test *型, 它的作用就是作为指向该结构体起始地址的指针,就是作为指向该结构体起始地址的指针,就是作为指向该结构体起始地址的指针, 而&((struct test *)0)->k  的作用便是求k到该起始指针的字节数。。。其实是求相对地址,起始地址为0,则&k的值便是size大小(注:打印时因为需要整型,所以有个int强转)所以我们便可以求我们需要的 size 了  。 好吧,一不小心把 offsetof() 函数的功能给讲完了:::

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这次再看就顺眼了吧(底层为什么是这样我还是不懂。。。只知道这样确实可以) ,  所以offsetof()的作用就是求我们梦寐以求的size, 并以size_t形式返回(size_t: 无符号整型)。

(二) 内核编程的严谨性   

#define container_of(ptr, type, member) ({              \         
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \         
(type *)( (char *)__mptr - offsetof(type,member) );})
    这里我们只看第二行:
const typeof( ((type *)0)->member ) *__mptr = (ptr);  
  它的作用是什么呢? 其实没什么作用(勿喷勿喷,让我把话说完),但就形式而言 _mptr = ptr,  那为什么要要定义一个一样的变量呢??? 其实这正是内核人员的牛逼之处:如果开发者使用时输入的参数有问题:ptr与member类型不匹配,编译时便会有warnning, 但是如果去掉改行,那个就没有了,而这个警告恰恰是必须的(防止出错有不知道错误在哪里)。。。这严谨性可以吧

typeof( ((type *)0)->member )
   它的作用是获取member的类型仅此而已。至此基本结束

(三) 总结

       container_of(ptr, type,member)函数的实现包括两部分:

           1.  判断ptr 与 member 是否为同意类型

           2.  计算size大小,结构体的起始地址 = (type *)((char *)ptr - size)   (注:强转为该结构体指针)

    现在我们知道container_of()的作用就是通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。

    container_of(ptr,type,member),这里面有ptr,type,member分别代表指针、类型、成员。

 


#include <stdio.h>  
#include <stdlib.h>
//获取结构体成员相对于结构体的偏移 
#define offsetof(TYPE,MEMBER) ((int) &((TYPE *)0)->MEMBER)  
//通过获取结构体中的某个成员的,反推该结构体的指针 
#define container_of(ptr, type , member) ({ \
    const typeof(((type *)0)->member) *__mptr = (ptr) ; \
    (type *)((char *)__mptr - offsetof(type,member)) ;})
 
#pragma pack(4)  
struct ptr  
{  
    char a ;   
    short b ;   
    int  c ;   
    double d ;  
};  
#pragma pack()  
int main(void)  
{  
    struct ptr Pt ;  
    struct ptr *pt ;
    printf("ptr:%d\n",sizeof(struct ptr));//16  
    //获取结构体的首地址 
    printf("ptr:%p\n",&Pt); //0028FEA8
    Pt.a = 'a';
    Pt.b = 2 ;
    Pt.c = 4 ;
    Pt.d = 12.04 ;
    //通过container_of获取结构体的首地址 
    pt = container_of(&Pt.c, struct ptr , c);
    printf("pt:%p\n",pt);  //0028FEA8
    printf("a:%c\n",pt->a) ;  //'a'
    printf("b:%d\n",pt->b) ;  //2
    printf("c:%d\n",pt->c) ;  //4
    printf("d:%.2lf\n",pt->d);//12.04
    return 0 ;  
}  

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值