由结构体成员获取结构体地址
在看操作系统的时候看到的,好骚啊。直接暴力使用结构体的地址进行推断,其间还巧妙地利用了编译器的优化原理。据说该方法广泛用于Linux内核。
使用宏定义,代码分三行,定义三层操作:
注意:由于在#define
操作中使用空格符号作为定义与替换内容的分隔符,所以在定义时不要加入空格。替换内容中似乎可以包含空格,因为该宏指令没有第三个参数
第一行:对外提供功能的函数名
#define member2parent(member_ptr,member_name) \
to_struct(member_ptr, struct Parent, member_name)
其中这里的member_ptr
是需要用来寻找结构体首部地址的成员变量地址,member_name
则不是变量,而是该成员变量在结构体中定义的名称。这里巧妙地利用了宏定义的替换原理,能够传递非变量的元素。在这里将结构体的定义加进去,能够减少使用时的代码量(避免重复输入结构体名)。
第二行:实际上进行由成员地址计算出结构体首地址操作的部分
#define to_struct(member_ptr,type,member_name) \
((type*)(char*)(member_ptr) - offset_of(member_name, type))
这里做的事情不但将结构体的首地址计算出来,还将格式转换为了type
类型的指针。又调用了一个新的宏offset_of
。
第三行:获取成员变量相对结构体首部的偏移
#define offset_of(type,member) ((size_t)(&(((type*)0)->member)))
https://blog.csdn.net/changqing1990/article/details/85256717
这里获取偏移的方法就很巧妙了。所使用的思路是,假设一个结构体的实体从0地址开始,那么只要取得成员变量的地址,就相当于取得了该成员变量相对于结构体首部的偏移。但是仔细观察的话,会发现一个问题:((type*)0)->member
这么一个操作,所做的事情是取得一个指针对应的成员变量的值,对C语言稍有了解的话,会知道其实NULL和0是一个东西。对空指针取成员变量值,不会出错吗?实际上外面还有一层操作:取地址符&
。如同上文链接所述,实际上很容易理解,对一个地址取值再取地址,可以直接简化为地址的变换操作。由于编译器发现这个值在过程中并未使用,于是就进行了这种优化,实际上0地址的数据并未被真实访问过,而我们也按照预期取得了偏移量的数值。