宏定义offsetof与container_of理解


前言

本文主要讲解的是linux内核中两个常用的宏offsetof与container_of


提示:以下是本篇文章正文内容,下面案例可供参考

一、宏定义offsetof

1.定义

#ifndef offsetof
#define offsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)
#endif

2.作用

offsetof主要用于计算结构体TYPE中MEMBER成员的偏移位置。

3.理解

现在,我们来进一步挖掘剖析该代码。

#define offsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)

其中TYPE为结构体类型,MEMBER为该TYPE结构体中的某个成员。
使用offsetof(TYPE,MEMBER),就可以得到MEMBER成员在TYPE结构体中的偏移位置,也就是偏移量了。
接下来我们看代码块中让我们感到复杂的定义是咋样的,从里向外进行剖析,大家首先看到的是0这个数被强制转换成TYPE类型的指针,通过箭头操作符去访问它的MEMBER成员。但在0地址处是肯定没有TYPE变量的,因为0地址处是留给操作系统使用的。但为啥这样做不会导致程序的崩溃呢?那我们就得考虑编译器干了啥事了。
我们必须得清楚的知道的事是:
1.编译器在编译时就已经清楚的知道结构体成员变量的偏移位置。
2.通过结构体变量首地址与偏移量定位成员变量。
(编译器干的事)示例;

#include<stdio.h>
#ifndef offsetof
#define offsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)
#endif
struct AR{
	int a;
	int b;
	int c;
};
void fun(struct AR* st){
	int *p1 = &(st->a);
    int *p2 = &(st->b);
    int *p3 = &(st->c);
    printf("%p\n",p1);
    printf("%p\n",p2);
    printf("%p\n",p3);
}
int main(){
struct AR s = {0};
fun(&s);
fun(NULL);
printf("offsetof a: %ld\n",offsetof(struct AR,a));
printf("offsetof b: %ld\n",offsetof(struct AR,b));
printf("offsetof c: %ld\n",offsetof(struct AR,c));
return 0;
}

其中输出结果为结构体s中成员的地址。当我们用空指针NULL来调用fun()函数时,即fun(NULL)输出结果为其结构体成员的偏移量,与其offsetof一致。
那我们就知道了该宏定义在经过编译器编译时是不会真正访问0地址处,具体的只是编译器做了一个加法而已,通过0地址+MEMBER在结构体中的偏移量转换成无符号的整数。(并不会如我们所想那样造成系统崩溃)
在这里插入图片描述
宏定义offsetof理解就写到这,接下来看另一个。

二、宏定义container_of

1.定义

#ifndef container_of
#define container_of(ptr,type,member)({const typeof(((type*)0)->member)*_mptr =(ptr);(type*)((char*)_mptr-offsetof(type,member));})
#endif

2.作用

有时候我们需要从成员变量的指针来获得对象所在的地址,这时候可以使用container_of宏实现

3.理解

首先,我们先进一步挖掘分析代码。
第一,({})是干嘛的,它是GUN C编译器的语法扩展,与逗号表达式类似,结果为最后一个语句的值。

#include<stdio.h>
#ifndef offsetof
#define offsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)
#endif

int main(){
int m=0;
int n=0;
int r=(m=1,n=2,m+n);
printf("r=%d\n",r);
int r1=({int m = 1;int n =2; m+n;});
printf("r1=%d\n",r1);
return 0;
}

在这里插入图片描述
第二,typeof是干嘛的。typeof是GUN C编译器的特有关键字。typeof只在编译期生效,用于得到变量的类型。

#include<stdio.h>
int main(){
int m = 21;
typeof(m) n = m;
const typeof(m)* p = &n;
printf("sizeof(n)=%ld\n",sizeof(n));
printf("n=%d\n",n);
printf("*p=%d\n",*p);
return 0;
}

在这里插入图片描述
第三,原理
在这里插入图片描述
接下来通过上面的理解,我们就可以理解该宏了。

#ifndef container_of
#define container_of(ptr,type,member)({const typeof(((type*)0)->member)*_mptr =(ptr);(type*)((char*)_mptr-offsetof(type,member));})
#endif

其中可以知道该宏的结果为最后一条语句的结果。
输入指向member的指针,结构体类型与member。通过该成员变量的地址减去其偏移量,返回指向成员member变量所在的结构体的首地址。
举例说明:

#include<stdio.h>
#ifndef offsetof
#define offsetof(TYPE,MEMBER)((size_t)&((TYPE*)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr,type,member)({const typeof(((type*)0)->member)*_mptr =(ptr);(type*)((char*)_mptr-offsetof(type,member));})
#endif
struct ST{
int a;
int b;
int c;	
};
int main(){
struct ST s={0};
char* ps=&s.c;
struct ST *p=container_of(ps,struct ST,c);
printf("&s=%p\n",&s);
printf("&p=%p\n",p);
return 0;
}

在这里插入图片描述
可能大家会觉得奇怪,那第二行代码是干嘛的呢?我们不要它也行呀,其实不然。

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

这行代码作用是将ptr的类型进行检查。如果不加这行代码,使用我们自己定义的宏时,若输入参数出错时,不会报错,只有一个警告。但使用这行代码时,会发生报错。(大家可以自定义一个宏,不加前面这行代码进行测试)

可能有人觉得我都知道该结构体地址,为啥要使用该宏求出呢。其实,该宏的作用是在不知道其结构体的地址,只知道结构体的成员变量地址时,通过其成员变量,求出其结构体地址。

container_of就理解到这。

总结

以上就是今天要讲的内容,仅仅简单介绍了宏offsetof与container_of的理解。本文是通过观看唐老师视频个人所记笔记理解,希望能给和我相同学习的人们一点帮助。同样,希望大家能够批评指正我所学的错误理解所在,在此先感谢大家的批评指正了!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

年少不想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值