转自: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. 自学Linux Shell3.3-列表命令ls

    点击返回 自学Linux命令行与Shell脚本之路 3.3-列表命令ls ls命令用于显示文件目录列表,和Windows系统下DOS命令dir类似.当执行ls命令时,默认显示的只有非隐藏文件的文件名. ...

  2. Canvas与javaScript特效笔记

    第六章   Canvas与javaScript特效笔记 q  <canvas>标签的用途 HTML5 canvas 提供了通过 JavaScript 绘制图形的方法,此方法使用简单但功能强 ...

  3. SpringBoot集成RocketMQ

    实战,用案例来说话 前面已经说了JMS和RocketMQ一些概念和安装,下面使用SpringBoot来亲身操作一下. 生产者的操作 SpringBoot项目创建完成,引入依赖是第一步: <dep ...

  4. Python面向对象编程和模块

    在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象. 编写类时,你定义一大类对象都有的通用行为.基于类创建对象时,每个对象都自动具备这种通用行为,然后根据需要赋予每个对象独 ...

  5. JAVA 泛型方法 和 静态方法泛型

    /* //  泛型方法和静态方法泛型 泛型类定义的泛型 在整个类中有效 如果被方法使用 那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定 为了让不同方法可以操作不同类型  而且类型还 ...

  6. 洛谷P5206 数树

    题意: task0,给定两棵树T1,T2,取它们公共边(两端点相同)加入一张新的图,记新图连通块个数为x,求yx. task1,给定T1,求所有T2的task0之和. task2,求所有T1的task ...

  7. Pycharm激活、配置以及快捷方式 | 图解

    访问flyai.club,一键创建你的人工智能项目 来源 | Python (python6359) Pycharm可以去官网下载 Pycharm的安装激活 jar包的目的就是让截获截止时间并骗过py ...

  8. 弹指之间 -- Polychord

    CHAPTER 19 复合和弦 Polychord 示例歌曲:爱很简单,恰是你的温柔

  9. C# Winform 按回车键查找下一个可设置焦点的组件

    private void frmLogin_KeyPress(object sender, KeyPressEventArgs e) { //按回车键查找下一个可设置焦点的组件. if (e.KeyC ...

  10. django在读取数据库时未筛选到符合条件的记录会报错

    (1)报错情况如下: DoesNotExist: Publisher matching query does not exist.   (2)处理方法: try:    p = Publisher.o ...