zoukankan      html  css  js  c++  java
  • container_of 和 offsetof 宏详解

    在linux内核链表中,会遇到两个宏。

    在include/linux/stddef.h中,有这样的定义

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

    这里的TYPE表示某个结构体类型,MEMBER表示结构体中的一个成员,这个宏求出了成员在结构体中的位置偏移(以字节为单位)

    如果你还不理解,我们举个例子吧。

    struct student 
    {
    	char name[20];
    	unsigned char age;
    };
    
    int main(void)
    {
    	int off = offsetof(struct student, age);
    	printf("off = %d
    ",off);
    }
    
    运行结果是off = 20


    其实宏里面的0只是一种特殊情况而已。对这个宏的解释是:假设结构体处于0x1234这个地址,

    ((TYPE*)0x1234 )-> MEMBER
    ->比类型转换的优先级高,所以要加括号。上面就得到了成员,再取地址,得到成员的地址

    &((TYPE*)0x1234 )-> MEMBER

    不用加括号,因为&的优先级比较低

    再来一个类型转换

    (size_t)&((TYPE*)0x1234 )-> MEMBER

    终于得到成员的起始地址了,还没有完,既然算偏移,就要减去结构体的起始地址

    (size_t)&((TYPE*)0x1234 )-> MEMBER  - 0x1234

    不难看出,这里的0x1234换成什么数字都可以,因为偏移是和起始地址无关的。

    不信的话可以把这个宏定义改一改,再测试一下

    #define offsetof(TYPE, MEMBER) (  (size_t) &((TYPE *)0x2222)->MEMBER  -   0x2222  )
    
    
    struct student 
    {
    	char name[20];
    	unsigned char age;
    };
    
    int main(void)
    {
    	int off = offsetof(struct student, age);
    	printf("off = %d
    ",off);
    }
    
    改成0x2222后,结果还是20.

    既然什么数字都可以,那就改成0吧,于是后面的-0就可以省略了。于是就得到了开头的那个宏。


    下面我们说另外一个宏。

    /**

    827  * container_of - cast a member of a structure out to the containing structure

    828  * @ptr:    the pointer to the member.

    829  * @type:   the type of the container struct this is embedded in.

    830  * @member: the name of the member within the struct.

    831  *

    832  */

    833 #define container_of(ptr, type, member) ({         

    834     const typeof( ((type *)0)->member ) *__mptr = (ptr);   

    835     (type *)( (char *)__mptr - offsetof(type,member) );})


    这个宏就是从一个成员的地址得到这个结构体的地址,俗称小指针转大指针。

    继续举例子。

    struct student 
    {
    	char name[20];
    	unsigned char age;
    };
    
    int main(void)
    {
    	struct student stu = {"wangdong",22};
    	
    	printf("&stu = %p
    ",&stu);
    	printf("&stu.age = %p
    ",&stu.age);
    
    	struct student *p = container_of(&stu.age, struct student, age);
    	printf("p= %p
    ",p);
    }
    运行结果为

    &stu = 0x7fff53df9c40

    &stu.age = 0x7fff53df9c54

    p= 0x7fff53df9c40

    第一行和第三行的值是一致的。

    如果你不理解这个宏的定义,我们先简化一下它,这样写
    #define container_of(ptr, type, member)             (  (type *)( (char *)ptr - offsetof(type,member) )  )
    (char *)ptr 成员的地址
    offsetof(type,member)  成员的偏移
    二者相减,就是结构体的起始地址,最后再加个强制类型转换。
    可是为什么不是这样写的呢,而是要多出来一行

    const typeof( ((type *)0)->member ) *__mptr = (ptr);

    没有这行到底行不行,其实也行,用上面的例子,去掉这行,也可以得到一样的结果。

    先看看这行什么意思吧, ((type *)0)->member 这是成员,typeof( ((type *)0)->member ) 得到了成员的类型,假设就是unsigned char类型,

    const typeof( ((type *)0)->member ) *__mptr 定义了一个这个类型的指针

    const unsigned char * __mptr = (ptr); 赋值给定义的这个指针。

    我苦思冥想,又结合网上的资料,认为这样写是做了一个类型检查。如果有了这行,假设结构体就没有member这个成员,那么编译会报错。

    所以这样写保证了member确实是type的一个成员。

    还有,这样写也保证了ptr确实是这个成员类型的指针,如果不是,编译也会报错。再做个实验。

    把刚才的代码改一下

    struct student *p = container_of((int *)&stu.age, struct student, age);

    故意把&stu.age转换成int*,编译就会报警告:

    test.c:33:22: warning: incompatible pointer types initializing 'const typeof (((struct student *)0)->age)

          *' (aka 'const unsigned char *') with an expression of type 'int *' [-Wincompatible-pointer-types]

            struct student *p = container_of((int *)&stu.age, struct student, age);

                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    test.c:11:39: note:expanded from macro 'container_of'

            const typeof( ((type *)0)->member ) *__mptr = (ptr);   

                                                 ^        ~~~~~


    如果没有这一行,那么就不会报错。

    所以,作者的出发意图是千方百计地让程序员写出安全的代码啊!真的是用心良苦。

    (完)

  • 相关阅读:
    SQLMAP注入教程-11种常见SQLMAP使用方法详解
    VS2012/2013/2015/Visual Studio 2017 关闭单击文件进行预览的功能
    解决 IIS 反向代理ARR URLREWRITE 设置后,不能跨域跳转 return Redirect 问题
    Spring Data JPA one to one 共享主键关联
    JHipster 问题集中
    Spring Data JPA 定义超类
    Spring Data JPA查询关联数据
    maven命名
    maven仓库
    Jackson读取列表
  • 原文地址:https://www.cnblogs.com/longintchar/p/5224435.html
Copyright © 2011-2022 走看看