struct ABC
{
int a;
int b;
int c;
};
+----------+ <------我们需要计算的是这个地址。
| a(4Byte) |
+----------+ <------这个地址是已知的。
| b(4Byte) |
+----------+
| c(4Byte) |
+----------+
通过上图可看出,只需要把当前知道的成员变量的地址ptr,减去它在结构体当中相对偏移量(4),就得到了结构体的首地址(ptr-4).
设计一个type类型的结构体,起始地址为0,编译器将结构体的起始的地址加上此结构体成员变量的偏移得到此结构体成员变量的偏移地址,由于设计的结构体起始地址为0,所以此结构体成员变量的偏移地址就等于其成员变量在结构体内的距离结构体开始部分的偏移量。
Linux内核中,用两个非常巧妙地宏实现了,一个是offsetof宏,另一个是container_of宏:
1.offsetof宏(获得一个结构体变量成员在此结构体中的偏移量)
#define offsetof(struct_type, member_name) ((size_t) & ((struct_type *)0)->member_name )
【分析】:
(1) 该宏中,struct_type为结构体类型,member_name为结构体内的变量名
(2) ((struct_type *)0) 是欺骗编译器说有一个指向结构struct_type 的指针,其地址值0
(3) &((struct_type *)0)->member_name 是要取得结构体struct_type中成员变量member_name的地址.
(4) 最后将其值强转为size_t,即转化为一个常数值,而不是将其当作地址进行使用。
因为基址为0,所以,这时member_name的地址当然就是member_name在struct_type中的偏移了。
2. container_of宏(从结构体[struct_type]某成员变量[member_name]指针[member_addr]来求出该结构体[struct_type]的首指针)
#define container_of(member_addr, struct_type, member_name) ({const typeof( ((struct_type *)0)->member_name ) *__mptr = (member_addr); (struct_type *)( (char *)__mptr - offsetof(struct_type,member_name) );})或者直接定义#define container_of(member_addr, struct_type, member_name) ((struct_type *)( (char *)member_addr - offsetof(struct_type,member_name) ))
【分析】:
(1)typeof( ( (struct_type *)0)->member )为取出member_name成员的变量类型。
(2) 定义__mptr指针member_addr为指向该成员变量的指针(即指向member_addr所指向的变量处)
(3) (char *)__mptr - offsetof(struct_type,member_name)) 用该成员变量的实际地址减去该变量在结构体中的偏移,来求出结构体起始地址。
(4) ({ })这个扩展返回程序块中最后一个表达式的值。