1. 程式人生 > >內核中container_of宏的詳細分析【轉】

內核中container_of宏的詳細分析【轉】

按位與 指針常量 並且 cast 例子 找到 eof 運算符 gnu

轉自: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宏的詳細分析【轉】