宏的作用: 计算结构体成员的偏移,从结构类型的开头返回字段成员的偏移量。
宏定义:#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
TYPE:结构名称(譬如struct definestruct)
((TYPE*)0): 取整数零并将其转换为指向TYPE 的指针。
((TYPE*)0)->m: 引用指向结构成员 m 的指针。
&(((TYPE*)0)->m):计算 m 的地址。
(size_t)&(((TYPE *)0)->m): 将结果转换为适当的数据类型。
/*
time:2020年8月31日14:14:34
objective:理解宏offsetof的用途
function:自动计算结构体中变量的偏移量
author:wu_junwu
*/
#include<stdio.h>
struct definestruct
{
int a;
char b;
short c;
long d;
}__attribute__((packed)); //__attribute__((packed))取消对齐访问,注意:attribute前后下划线分别是两段
/*
设置对齐访问
struct definestruct
{
int a;
char b;
short c;
long d;
}__attribute__((aligned(4)));设置4字节对齐访问
*/
//(type*)0定义0地址为type *类型,该类型指针指向一个type类型的结构体首地址,
//然后以type.member的方式访问结构体中的变量member,最后用&得到member的地址,也就是偏移量
#define offsetof(type,member) ((int)&((type*)0)->member) //强制转换类型为int
int main(void)
{
struct definestruct ds;
struct definestruct *p2=NULL;
p2=&(ds);
int *p;
p=&(ds.a);
printf("结构体首元素地址p=%p\n",p);
printf("结构体首元素地址ds=%p\n",&ds);
printf("结构体首元素地址&(ds.a)=%p\n",&(ds.a));
printf("ds2.b=%p\n",p2);
printf("ds.b=%p\n",p);
printf("结构体元素a的偏移量=%d\n",offsetof(struct definestruct,a));
printf("结构体元素a的偏移量=%d\n",offsetof(struct definestruct,b));
printf("结构体元素a的偏移量=%d\n",offsetof(struct definestruct,c));
printf("结构体元素a的偏移量=%d\n",offsetof(struct definestruct,d));
printf("变量b的首地址=%d\n",(int)&ds.b-(int)&ds.a);//结果是4;该处的4表示int整型,代表4个字节
printf("变量b的首地址=%d\n",(char*)&ds.b-(char*)&ds.a);//结果是4;该处的4表示的是指针char*,代表4个字节。
printf("变量b的首地址=%d\n",(int *)&ds.b-(int*)&ds.a);//结果是1;该处的1表示的是指针int*,代表4个字节。
/************************************************************
为什么指针类型的1代表的字节数是4呢?
解答:因为指针本质就是地址,在32位系统中,内存地址是就是按照4字节读取的,地址(指针)加上1就表示加上了4字节的长度
**************************************************************/
//这样定义指针p1会导致编译错误:assignment from incompatible pointer type
//指针类型不兼容;正确的应该将指针p1定义为int *p1=NULL
/*
struct definestruct *p1=NULL;
p1=&(ds.a);
printf("ds1.a=%p\n",p1);
*/
return 0;
}
/*
conclusion:
offsetof宏的原理:
(1)(type*)0 :这是一个强制类型转换,定义一个类型为type *的地址0,pointer1=0
该0地址(指针)指向的对象是一个type类型(在这个例子中是struct definestruct类型)的结构体的首地址,也可以说是结构体中首元素的地址,他们的地址相同。
(2)此时结构体的地址首地址为0;((type*)0)—>member ((type*)0)表示一个type类型指针,
通过该指针访问这个结构体中的变量member。
(3)&((type*)0)—>member 等价于&(((type*)0)->member),得到变量member的地址。
假设得到的地址是ox88888888,此时的指针pointer2=&(((type*)0)->member)=ox88888888,
那么偏移量为subp=pointer2-pointer1;因为我们再第一步就将结构体的首地址强制转换成type*类型的0地址
所以,指针pointer2既是我们变量member的地址也是变量member的偏移量。
*/
结构体首元素地址p=0xbfaeb04d
结构体首元素地址ds=0xbfaeb04d
结构体首元素地址&(ds.a)=0xbfaeb04d
ds2.b=0xbfaeb04d
ds.b=0xbfaeb04d
结构体元素a的偏移量=0
结构体元素a的偏移量=4
结构体元素a的偏移量=5
结构体元素a的偏移量=7
变量b的首地址=4
变量b的首地址=4
变量b的首地址=1