转自: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. NOIP2018退役记

    退役失败*1. md可能只有一次啊.

  2. HGOI 20190217 题解

    /* for me,开训第一天 /beacuse 文化课太差被抓去补文化课了... 看一眼题 : AK局? 但是,Wa on test #10 in problem C 290! (就差那么一咪咪) ...

  3. OneProxy构建

    OneProxy的主要功能有:1. 垂直分库 2. 水平分表 3. Proxy集群 4. 读高可用 5. 读写分离(master不参与读) 6. 读写分离(master参与读) 7. 写高可用 8. ...

  4. JDK源码分析(6)ConcurrentHashMap

    JDK版本 ConcurrentHashMap源码分析 table:默认为null,初始化发生在第一次插入操作,默认大小为16的数组,用来存储Node节点数据,扩容时大小总是2的幂次方. nextTa ...

  5. dij与prim算法

    两种算法本质是相同的. 都是从某一个点开始进行延伸,不断更新一个dis值,直到所有的点都被遍历到,从而求出一个最短路或者是一个树的边权的最小总和. 朴素算法都是n^2,都可以采用堆优化处理,降低复杂度 ...

  6. 洛谷P3952 时间复杂度

    大毒瘤...... 时隔快半年我终于花了两个小时堪堪A掉这一题...果然我还没有准备好. 想法:用DFS模拟递归. 时间复杂度的处理:每层循环取max,然后相加. 最大难点:各种繁杂而令人发指的特判. ...

  7. MATLAB:增加噪声,同时多次叠加噪声图和原图以及求平均图像(imnoise,imadd函数)

    本次涉及了对原图像增加高斯噪声.多次叠加原图和高斯噪声图以及叠加后的平均图像. close all; %关闭当前所有图形窗口,清空工作空间变量,清除工作空间所有变量 clear all; clc; R ...

  8. 基于tcp和多线程的多人聊天室-C语言

    之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...

  9. C# TextBox猜想输入和历史记录输入

    什么是猜想输入 当我们在使用查询应用时,输入某个关键字后文本框下面就会自动弹出跟此关键字相附的词组供我们选择,为我们节省了输入的时间,这就是猜想输入,例如:百度,谷歌,等等,如下图: 如上图所示,使用 ...

  10. python os模块 常用命令

    python编程时,经常和文件.目录打交道,这是就离不了os模块.os模块包含普遍的操作系统功能,与具体的平台无关.以下列举常用的命令 1. os.name()——判断现在正在实用的平台,Window ...