前言
本文主要讲解的是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的理解。本文是通过观看唐老师视频个人所记笔记理解,希望能给和我相同学习的人们一点帮助。同样,希望大家能够批评指正我所学的错误理解所在,在此先感谢大家的批评指正了!