typedef struct student
{
int id;
char name[30];
int math;
} Student;
int main()
{
Student stu;
stu.id = 123456;
strcpy(stu.name,"feizhufeifei");
stu.math = 90;
stu.PE = 80;
printf("Student:%p\r\n",&stu);
printf("stu.ID:%p\r\n",&stu.ID);
printf("stu.name:%p\r\n",&stu.name);
printf("stu.math:%p\r\n",&stu.math);
return 0;
}
/** 打印结果如下: */
//结构体的地址
// Student:0xffffcbb0
//结构体第一个成员的地址
// stu.ID:0xffffcbb0 //偏移地址 +0
// stu.name:0xffffcbb4//偏移地址 +4
// stu.math:0xffffcbd4//偏移地址 +24
我们可以看到,结构体的地址和结构体第一个成员的地址是相同的( x )。
不太理解的再看下这两个例子:
以上情况只针对 POD 类型的结构体,具体见《深入理解c++11: c++11的特性与使用》POD类型章节 , 对于非POD类型是不成立的,但是这并不影响利用成员变量的偏移地址计算结构体首地址。例如:
#include
#include
#include
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
typedef struct chinese {
private:
int a;
char b;
} Chinese;
typedef struct studenta : Chinese
{
int id;
char name[30];
int math;
} ChinaStudent;
typedef struct studentb
{
int id;
char name[30];
int math;
}Student;
int main()
{
ChinaStudent a;
Student b;
printf("a.id: %d, a: %d\n", &a.id, &a);
printf("b.id: %d, b: %d\n", &b.id, &b);
return 0;
}
/** 打印结果 */
// a.id: 1827697936, a: 1827697928
// b.id: 1827697888, b: 1827697888
我们可以看到,结构体中成员变量在内存中存储的其实是偏移地址。也就是说 结构体A的地址+成员变量的偏移地址 = 结构体成员变量的起始地址。
因此,我们也可以根据 结构体变量的起始地址和成员变量的偏移地址来反推出结构体A的地址。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
首先看下三个参数:
container_of宏的作用是 通过结构体内某个成员变量的地址和该变量名,以及结构体类型, 找到该结构体变量的地址。这里使用的是一个利用编译器技术的小技巧,即 先求得结构成员在结构中的偏移量,然后根据成员变量的地址反过来得出主结构变量的地址。下面具体分析下各个部分。
首先看下typeof,是用于返回一个变量的类型,这是GCC编译器的一个扩展功能,也就是说typeof是编译器相关的。既不是C语言规范的所要求,也不是某个标准的一部分。
int main()
{
int a = 5;
//这里定义一个和a类型相同的变量b
typeof(a) b = 6;
printf("%d,%d\r\n",a,b);//5 6
return 0;
}
((TYPE *)0) 将0转换为 type 类型的结构体指针,换句话说就是让编译器认为这个结构体是开始于程序段起始位置0,开始于0地址的话,我们得到的成员变量的地址就直接等于成员变量的偏移地址了。
(((type *)0)->member) 引用结构体中MEMBER成员。
这句代码意思是用 typeof() 获取结构体里member成员属性的类型,然后定义一个该类型的临时指针变量 __mptr,并将ptr所指向的member的地址赋给 __mptr;
为什么不直接使用 ptr 而要多此一举呢?我想可能是为了避免对 ptr 及 ptr 指向的内容造成破坏
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
size_t 是标准C库中定义的,在32位架构中被普遍定义为:
typedef unsigned int size_t;
而在64位架构中被定义为:
typedef unsigned long size_t;
官方解释是:取某个结构体成员与结构体本身起始地址的偏移大小
也就与官方解释对应上了
这句话的意思就是,把 __mptr 转换成 char* 类型。因为 offsetof 得到的偏移量是以字节为单位。两者相减得到结构体的起始位置, 再强制转换成 type 类型
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
typedef struct student
{
int id;
char name[30];
int math;
}Student;
int main()
{
Student stu;
Student *sptr = NULL;
stu.id = 123456;
strcpy(stu.name,"zhongyi");
stu.math = 90;
sptr = container_of(&stu.id,Student,id);
printf("sptr=%p\n",sptr);
sptr = container_of(&stu.name,Student,name);
printf("sptr=%p\n",sptr);
sptr = container_of(&stu.math,Student,id);
printf("sptr=%p\n",sptr);
return 0;
}
/** 运行结果/
// sptr=0x16ddf7510
// sptr=0x16ddf7510
// sptr=0x16ddf7510
参考文章1
参考文章2