前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >container_of宏定义分析

container_of宏定义分析

作者头像
知否知否应是绿肥红瘦
发布2025-02-19 21:21:13
发布2025-02-19 21:21:13
7800
代码可运行
举报
文章被收录于专栏:Linux知识Linux知识
运行总次数:0
代码可运行
container_of(ptr, type, member) 位于Linux内核源码Kernel.h中:

代码语言:javascript
代码运行次数:0
复制
#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*型,再做减法运算,这样保证最后的结果一定是对的。 

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

代码语言:javascript
代码运行次数:0
复制
const typeof( ((type *)0)->member ) *__mptr = (ptr); 

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

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

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

代码语言:javascript
代码运行次数:0
复制
#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* 去掉看看会发生什么?

代码语言:javascript
代码运行次数:0
复制
  (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。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • container_of(ptr, type, member) 位于Linux内核源码Kernel.h中:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档