转自: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. 【BZOJ3456】城市规划(生成函数,多项式运算)

    [BZOJ3456]城市规划(生成函数,多项式运算) 题面 求\(n\)个点的无向连通图个数. \(n<=130000\) 题解 \(n\)个点的无向图的个数\(g(n)=2^{C_n^2}\) ...

  2. LoadRunner断言:正确结果有多种情况

    用过lr的都知道,我们可以用web_reg_find这个注册函数判断某个请求的返回中是否有我们要找的内容. 但是,如果正常的情况有多种,该如何断言呢? 设置多个检查点 把每个检查点获取到的结果数量相加 ...

  3. nowcoder172A 中位数 (二分答案)

    二分一下答案,假设是x. 我们把大于x的看成1,小于x的看成-1,等于x的看成0 那某个区间的和如果是正的,就说明这个区间中位数大于x:如果是0,就等于x:如果是负的,就小于x: 这样的话,做一个前缀 ...

  4. 洛谷P2668 斗地主

    好,终于搞完了这一道毒瘤题...... 先想到搜索,然后想到状压,发现数据组数很多,又是随机,还是决定用搜索. 先搜出的多的,于是顺序是三个顺子,然后按照多到少搜带牌,最后是不带牌. 大体思路很简单, ...

  5. 【洛谷P4180】严格次小生成树

    题目大意:给定一个 N 个顶点,M 条边的带权无向图,求该无向图的一个严格次小生成树. 引理:有至少一个严格次小生成树,和最小生成树之间只有一条边的差异. 题解: 通过引理可以想到一个暴力,即:先求出 ...

  6. 爬虫acm比赛成绩(多页成绩整合在一起、获取复制不了的数据)(hihocoder、计蒜客)

    https://github.com/congmingyige/web-crawler_rank-of-competition-in-JiSuanKe-and-hihocoder 1. 计蒜客(获取复 ...

  7. poj 2785(折半枚举+二分搜索)

    传送门:Problem 2785 题意: 给定 n 行数,每行都有 4 个数A,B,C,D. 要从每列中各抽取出一个数,问使四个数的和为0的所有方案数. 相同数字不同位置当作不同数字对待. 题解: 如 ...

  8. (stack)Train Problem I hdu1022

    Train Problem I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  9. sizeof 与 字节对齐

    转:http://baike.baidu.com/view/1356720.htm sizeof是运算符,可用于任何变量名.类型名或常量值,当用于变量名(不是数组名)或常量时,它不需要用圆括号.    ...

  10. Nginx插件之openresty反向代理和日志滚动配置案例

    Nginx插件之openresty反向代理和日志滚动配置案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.openresty介绍 1>.Nginx介绍 Nginx是一款 ...