您的位置:首页 > 大数据 > 人工智能

container_of 宏、offsetof 宏 分析

2016-05-02 14:02 633 查看
container_of 是Linux中常用的宏,其作用就是根据结构体成员变量的地址获取结构体的地址。

container_of 在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)); })


这么一个宏定义,可以说是把C语言的指针运用的出神入化。这个宏可以分三步来解读。

第一步:定义了一个与 ptr 相同类型的指针 __mptr,这个指针类型通过 typeof(((type *)0)->member) 来获得,然后将__mptr 赋值为 ptr。

typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛,其作用就是通过一个变量获取它的类型。

要获取结构体中member成员的类型,首先将零地址强制转换为结构体类型指针( (type *)0 ),再通过这种指针得到这个结构体的 member 成员变量((type*)->member),然后通过这个变量得到它的类型(typeof(((type *)0)->member) )。

第二部:用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址。

member成员在结构体中的偏移地址通过 offsetof 宏得到:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


这个宏首先将零地址强制转换为结构体类型指针((type )0),再用这个指针得到MEMBER成员(((type *0)->MEMBER)),然后获取这个成员的地址(&(&((TYPE )0)->MEMBER)),最后将这个地址强转为size_t类型。因为这时候结构体首地址为0,所以成员变量的地址就是在结构体中断地址偏移。

第三步:用大括号将两条语句括起来,作为一个整体的语句块,在这个语句块外面在包一层小括号,使这个语句块的值可以被外部使用。

通过分析这个宏我才发现,一个语句块也是有返回值的,还能被外部使用,例如以下语句也是可以的:

int a = ({ int b = 2; int c = 3; b + c;});


总感觉为了实现container_of这个宏真是大费周章啊,用的着那么麻烦吗?自己写的话一行代码搞定:

#define container_of(ptr, type, member) (type *)((char *)ptr - offsetof(type, member))


乍一看似乎没毛病,仔细研究还是没毛病,但Linux内核中的定义方法自然有它的道理。当传入的指针ptr类型和结构中member成员类型不一样时,内核中的定义方法编译时会有一个指针类型不匹配的警告,而自己的定义方法悄无声息的编译通过了。不得不说,Linux开发人员考虑的真周到。

流云非晚
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: