转自:http://blog.chinaunix.net/uid-30254565-id-5637597.html

  1. 内核中container_of宏的详细分析
  2. 16年2月28日09:00:37
  3. 内核中有一个大名鼎鼎的宏-----container_of();这个宏定义如下所示,为了表示一下敬意,我就把注释一起粘贴下来了:
  4. /**
  5. * container_of - cast a member of a structure out to the containing structure
  6. * @ptr:    the pointer to the member.
  7. * @type:    the type of the container struct this is embedded in.
  8. * @member:    the name of the member within the struct.
  9. *
  10. */
  11. #define container_of(ptr, type, member) ({            \
  12. const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
  13. (type *)( (char *)__mptr - offsetof(type,member) );})
  14. 先来说这个宏的意义:它根据结构体中某成员变量的指针来求出指向整个结构体的指针。指针类型从结构体某成员变量类型转换为 该结构体类型。
  15. 比如先定义一个结构体:
  16. struct test {
  17. char name[20] ;
  18. char i;
  19. int j;
  20. };
  21. 假如,我们不小心知道了变量j的地址,那么我们想要通过j的地址来找到整个结构体test的地址,怎么来找呢???
  22. (一) offsetof宏:
  23. 结构体是一个线性存储的结构,无论在哪存放,j相对于整个结构体的地址的偏移值是不变的,于是,如果我们能够求出来这个偏移值的话,那么用j的地址减去这个偏移值不就是整个结构体的地址么~这是一个朴素的想法,内核中也确实这么做的~关键是怎么求出这个偏移值?内核的非常聪明的采取了下面的方法:
  24. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  25. (1)首先通过(TYPE *)0将0转换为TYPE类型的指针;
  26. (2)((TYPE *)0)->MEMBER 访问结构中的数据成员;
  27. (3)&(((TYPE *)0)->MEMBER)取出数据成员的地址;
  28. (4)(size_t)(&(((TYPE*)0)->MEMBER))结果转换类型; 注意这里:这个&是取地址符号,不是按位与,注意运算符号的优先级。
  29. 巧妙之处在于将地址0强制类型转换成(TYPE*),结构体以内存空间首地址0作为起始地址,则各个结构体成员变量的偏移地址就等于其成员变量相对于整个结构体首地址的偏移量 。即:&(((TYPE *)0)->MEMBER)就是取出其成员变量的偏移地址,(size_t)(&(((TYPE*)0)->MEMBER))经过size_t的强制类型转换以后,其数值为结构体内的偏移量。
  30. 需要明确的一点是,地址就是地址,它没有类型之分,你把它强制转换成什么类型它就是什么类型,所以在c语言中有各种强制类型转换。
  31. 用下面的例子来说明:
  32. #include <stdio.h>
  33. struct test {
  34. char i;
  35. int j;
  36. int k;
  37. };
  38. int main(int argc, char const *argv[])
  39. {
  40. struct test *temp = 0;
  41. printf("%p \n", &(temp->j));
  42. printf("%d \n", (size_t) &(temp->j));
  43. printf("%p \n", &(temp->k));
  44. printf("%d \n", (size_t) &(temp->k));
  45. return 0;
  46. }
  47. 运行结果是:
  48. 0x4
  49. 4
  50. 0x8
  51. 8
  52. 可以看出来,通过采用这种方式,就可以求出来结构体中成员变量相对与整个结构体首地址的偏移量。
  53. (二) container_of宏
  54. 如果理解了上面的部分,再看这个container_of宏就不是那么难了,我们先想想它怎么实现:
  55. 假设我们知道一个test类型的结构体里面的一个成员变量j的地址,那么需要先求出这个j变量相对于整个结构体地址的偏移值,然后用这个j的地址减去这个偏移值就行了。但是还有一点,这个j变量的数据类型是什么样的?这个虽然我们知道test数据类型,但是我们怎么取出来j对应的数据类型呢?这时候就用到typeof关键字了,typeof是GNU
    C对标准C的扩展,它的作用是根据变量获取变量的数据类型。
  56. 下面来看这些代码:
  57. #define container_of(ptr, type, member) ({            \
  58. const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
  59. (type *)( (char *)__mptr - offsetof(type,member) );})
  60. 首先
  61. (1)((type *)0)->member为设计一个type类型的结构体,并且这个结构体的的起始地址为0,然后将它指向我们知道的member变量,然后通过typeof( ((type *)0)->member )来获得member对应的数据类型。
  62. (2)const typeof( ((type *)0)->member ) *__mptr = (ptr);意思是声明一个与member同一个类型的指针常量 *__mptr,并初 始化为ptr.
  63. (3)(char *)__mptr - offsetof(type,member)意思是__mptr的地址减去member在该struct中的偏移量得到的地 址, 这样得到的就是整个结构体的首地址。
  64. (4)得到首地址后还没有完,上面说了,地址只是一个地址,它没有数据类型,所以最后再进行一一次强制类型转换,转换成我们需要的type类型的,即:
  65. (type *)( (char *)__mptr - offsetof(type,member) );
  66. (5)({ })这个扩展返回程序块中最后一个表达式的值。注意这个 container_of宏是两个表达式语句的综合。 相当与顺序执行了两个语句,这时候得到的地址就是member成员所在结构体的首地址。
  67. 要注意的是代码高亮处 ,(char *)__mptr 的作用是将__mptr 强制转换为字符指针类型,必须的!!!如果__mptr为整形指针 __mptr - offset 相当于减去sizeof(int)*offset个字节!!!
  68. 下面再看一个程序来温习一下:
  69. #include <stdio.h>
  70. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
  71. #define container_of(ptr, type, member) ({            \
  72. const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
  73. (type *)( (char *)__mptr - offsetof(type,member) );})
  74. struct test {
  75. char name[20] ;
  76. char i;
  77. int j;
  78. };
  79. int main(int argc, char const *argv[])
  80. {
  81. struct test temp = {"zer0", 'a', 26};
  82. printf("&temp = %p.\n", &temp);
  83. printf("&temp.i = %p.\n", &temp.i);
  84. printf("&temp.j = %p.\n", &temp.j);
  85. printf("offset of i = %d.\n", offsetof(struct test, i));
  86. printf("offset of j = %d.\n", offsetof(struct test, j));
  87. printf("&temp = %p.\n", container_of(&temp.i, struct test, i));
  88. printf("&temp = %p.\n", container_of(&temp.j, struct test, j));
  89. struct test *tmp = container_of(&temp.i, struct test, i);
  90. printf("tmp->name : %s, tmp->i : %c, tmp->j : %d.\n", tmp->name, tmp->i,                                             tmp->j);
  91. return 0;
  92. }
  93. 运行结果如下所示:
  94. &temp = 0xbf8b41b0.
  95. &temp.i = 0xbf8b41c4.
  96. &temp.j = 0xbf8b41c8.
  97. offset of i = 20.
  98. offset of j = 24.
  99. &temp = 0xbf8b41b0.
  100. &temp = 0xbf8b41b0.
  101. tmp->name : zer0, tmp->i : a, tmp->j : 26.

内核中container_of宏的详细分析【转】的更多相关文章

  1. Linux内核中container_of宏的详细解释

    上一节拒绝造轮子!如何移植并使用Linux内核的通用链表(附完整代码实现)我们在分析Linux内核链表的时候注意到内核在求解结构体偏移的时候巧妙的使用了container_of宏定义,今天我们来详细剖 ...

  2. 剖析linux内核中的宏---------container_of

    #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); ...

  3. 内核中的宏定义__init、__initdata和__exit、__exitdata

    __init.__initdata和__exit.__exitdata的定义位于<kernel/include/linux/init.h> /* These are for everybo ...

  4. 关于Delphi中的字符串的详细分析

    关于Delphi中的字符串的详细分析   只是浅浅的解析下,让大家可以快速的理解字符串. 其中的所有代码均在Delphi7下测试通过. Delphi 4,5,6,7中有字符串类型包括了: 短字符串(S ...

  5. linux内核中的宏ffs(x)

    linux内核中ffs(x)宏是平台相关的宏,在arm平台,该宏定义在 arch/arm/include/asm/bitops.h #define ffs(x) ({ unsigned long __ ...

  6. 剖析linux内核中的宏-----------offsetof

    offsetof用于计算TYPE结构体中MEMBER成员的偏移位置. #ifndef offsetof#define offsetof(TYPE, MEMBER) ((size_t) &((T ...

  7. Linux内核中的宏:__init and __exit

    ZZ FROM: http://blog.csdn.net/musein/article/details/742609 ======================================== ...

  8. javascrip中cookie的使用详细分析

    JavaScript中的另一个机制:cookie,则可以达到真正全局变量的要求. cookie是浏览器 提供的一种机制,它将document 对象的cookie属性提供给JavaScript.可以由J ...

  9. js中cookie的使用详细分析

    JavaScript中的另一个机制:cookie,则可以达到真正全局变量的要求. cookie是浏览器 提供的一种机制,它将document 对象的cookie属性提供给JavaScript.可以由J ...

随机推荐

  1. 04 Zabbix4.0系统配置触发器trigger

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 04 Zabbix4.0系统配置触发器trigger 请点击查看Zabbix3.0.8版本trig ...

  2. 学习Spring Boot:(二十六)使用 RabbitMQ 消息队列

    前言 前面学习了 RabbitMQ 基础,现在主要记录下学习 Spring Boot 整合 RabbitMQ ,调用它的 API ,以及中间使用的相关功能的记录. 相关的可以去我的博客/RabbitM ...

  3. 12: MyBatis之传入参数parameterType

    源链接地址:http://blog.csdn.net/liaoxiaohua1981/article/details/6862764

  4. mysql 多表管理修改

    update t_res_ys,cms_article_data,cms_article set cms_article_data.jsdata=t_res_ys.jsdata ,cms_articl ...

  5. html实现猜数字游戏

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. oracle使用using关键字

    oracle使用using关键字sql/92标准可以使用using关键字来简化连接查询,但是只是在查询满足下面两个条件时,才能使 用using关键字进行简化.1.查询必须是等值连接.2.等值连接中的列 ...

  7. java包的所有类生成class

    javac的编译单位其实就是单个的java文件,为了达到同时编译多个java文件的目的,可以将所需编译的java文件路径保存在一个txt中,比如sourcelist.txt,以换行为分隔符(这个过程称 ...

  8. 强大的svg操作库——Raphael

    先常规先引入Raphael库: <script src="raphael.js" type="text/javascript"></scrip ...

  9. H5新特性之geolocation

    geolocation是H5新增的对象,它用于定位,继承在navigator对象内,以前用navigator只用到userAgent,现在就多了这个geolocation 有2种方法(getCurre ...

  10. JavaEE学习总结(十二)—MyEclipse开发工具与HTML

    一.MyEclipse MyEclipse,是在eclipse 基础上加上自己的插件开发而成的功能强大的企业级集成开发环境,主要用于Java.Java EE以及移动应用的开发.MyEclipse的功能 ...