前段时间,我在某公司面试期间,我从面试出来的同学那里得知,面试官会问他们一个问题:“在已知,某结构体T的某个域a所在的内存地址ptr的情况下,怎么获取a所在的对象的其实地址?”。也就是说:现在已知有struct T
类型,且其中T类型的对象中有个域叫int a
,在已知某个T
类型的对象的a
所在地址等于0x1100
的情况下,求a
所在的整个对象的起始地址。
struct T{
//...
int a;
//...
}
解题思路:
整个问题可以分两步求解,
(1)a对应的偏移地址offset;
(2)用已知的a地的址ptr减去offset就是整个对象的其实地址了;
个人解法:
幸好我在提前得知了这个问题,做了一些准备。想到了,我们
(a)求偏移量:
(struct T) *t1=new struct T;
unsigned int offset=&(t1->a)-t1;
先申请一个T 类型的对象*t1*,用*t1*的*a*所在的地址,减去*t1*的起始地址,就是a的偏移地址*offset*;
(b)求地址:
unsigned int res=ptr-offset;
return res;
用ptr减去offset就是我们想要的起始地址了。
contianer_of(ptr,type,member)解法:
container_of是linux中的一个宏,它的作用是通过结构体中某一成员的地址来获得该成员所在结构体的地址。
//偏移量
//include/linux/stddef.h
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
//做差
//include/linux/kernel.h
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
我们再来看看上面的解法,
它在求解偏移时使用宏
//include/linux/stddef.h
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
它将0转换为一个TYPE类型对象的起始地址,也就是假设给内存0x0000开始的地方放了一个TYPE类型的对象,由于(需要结构体对齐)在编译时对象的大小和每个属性的相对于起始地址的偏移量都确定了。这样一来,
(unsigned int )&((TYPE *)0)->MEMBER
MEMBER成员的地址,就是我们我们我们要求的偏移量了。
有了上面的偏移量。接下来就是求起始地址
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
这个宏当中,首先通过
const typeof( ((type *)0)->member ) *__mptr = (ptr);
申明了一个member自身类型类型的指针__mptr,并用ptr对其赋值,所以__mptr的现在的内容与ptr是相等的。
然后通过
(type *)( (char *)__mptr - offsetof(type,member) );
用__mptr减去上面求到的偏移量就是最终所需要的地址。