container_of宏定义分析

container_of(ptr, type, member) 位于Linux内核源码Kernel.h中:

#define offset_of(type, memb) \
    ((unsigned long)(&((type *)0)->memb))
#define container_of(ptr, type, member) ({            \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offset_of(type,member) );})

其作用是:已知某结构体的成员member和指向该成员的指针ptr(也就是member的地址),算出该结构体的起始地址。

下面简单的分析下:
首先我们需要知道,如果知道一个结构体中某成员的地址,我们还需要知道什么就能求出结构体的首地址?
答案很简单,就是该成员相对于首地址的偏移量,然后把该成员地址减去偏移量就是首地址,这个偏移量在结构体被初始化时已经被确定了,所以我们只需要知道偏移量即可。

那么如何求出偏移量呢?
这就需要另一个宏定义,也就是上面的offset_of(type, memb) ,它的作用就是求出结构体中某成员相对于首地址的偏移量,实现原理就是先把地址“0”强制转化成指向该结构体的指针,此时可以认为该结构体的首地址就是0,接着再取该结构体中某个成员的地址,因为首地址是0,所以这个地址既是该成员的地址,也是该成员地址相对于首地址的偏移量。

我们利用offset_of(type, memb)求出memb的偏移量之后,将memb的地址-偏移量即可得到结构体的首地址。

但是,需要注意的是,offset_of(type, memb)求出的偏移量是以字节为单位的,在不知道ptr指向的数据类型的情况下,需要把ptr强制转化成char*型,再做减法运算,这样保证最后的结果一定是对的。 

此时,我们发现程序好像多了一句:

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

这句话似乎是不需要的,其实,如果能保证输入的参数ptr一定是member的指针,这句话可以不需要。

这句话首先取出member的类型,然后将变量__mptr强制转化为指向member的指针,再把ptr赋给变量__mptr,若ptr不是指向member的指针,那么编译时便会生成警告,提醒用户参数错误。

下面,写一个测试程序来验证下(开发环境是QT,编译器是MinGW_32bit  GNU C标准):

#include <stdio.h>

#define offset_of(type, memb) \
    ((unsigned long)(&((type *)0)->memb))

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

struct dev
{
  char* name;
  int id;
  int number;
  unsigned char mode;
  unsigned int addr;
};
struct dev device;

int main()
{
    device.name = "Test";
    device.id = 3;
    device.number = 20;
    device.mode = 1;
    device.addr = 100;

    int* address = &device.number;//定义一个指针指向number成员

    printf("device=%d\n",&device);//打印结构体首地址 以及各个成员地址
    printf("device.name=%d\n",&(device.name));
    printf("device.id=%d\n",&(device.id));
    printf("device.number=%d\n",&(device.number));
    printf("device.mode=%d\n",&(device.mode));
    printf("device.addr=%d\n",&(device.addr));

    /*打印number成员的偏移量*/
    printf("offset_of =%d\n",offset_of(struct dev,number));
    
    /*根据number成员的地址求出结构体首地址*/
    printf("container_of =%d\n",container_of(address,struct dev,number));


    //printf("buf = %d\n",&buf[0]);
    //printf("buf+1 =%d\n",&buf[0]+1);

    return 0;
}

运行结果如下: 

现在,我们把刚刚说的那个指针强制转换成char* 去掉看看会发生什么?

  (type *)( (char *)__mptr - offset_of(type,member) );})

  (type *)(__mptr - offset_of(type,member) );})//换成这样

可以看到,算出来的结构体的首地址是42189912,这和真实的首地址4218936明显不符。

其原因就是指针的加减法问题,由于__mptr是指向number的指针,而number成员是int型的,int型在GNU  C中 占4个字节,

__mptr-8的实际含义是,减去8个int型占的空间,也就是减去8*4=32字节,所以最后的结果是4218944-8*4=4218912。

猜你喜欢

转载自blog.csdn.net/qq_24835087/article/details/104357187