关于Linux/kernel.h中的offsetof和container_of宏

一、

位于Linux/kernel.h中
#define offsetof(s,m) (size_t)&(((s *)0)->m)
#define container_of(ptr, type, member) ({   \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})


简单应用:
struct A_t
{
 char a;
 char b;
 int *c;
};
struct B_t
{
 char a;
 int  *c;
};
struct A_t A;
struct B_t B;
struct A_t *A_add;
A.c = (int *)malloc(sizeof(int),1);
B.c = &A.c;
dev = container_of(inode->i_cdev, struct scullc_dev, cdev);
A_add = container_of(B.c,struct A_t,c);


二、offsetof宏

#define offsetof(s,m) (size_t)&(((s *)0)->m)
ofssetof(s, m) 其中,s 是结构体名,m 是它的一个成员。s 和 m 同是宏  offsetof() 的形参,这个宏返回的是结构体 s 的成员 m 在结构体中的偏移地址。

(s *)0  :  这里的用法实际上是欺骗了编译器,使编译器认为 "0" 就是一个指向 s 结构体的指针(地址),还句话说 s 结构体就是位于 0x0 这个地址处。

(s *)0-> m : 自然就是指向这个结构体的 m成员。

&((s *)0)->m :  表示 m成员的地址。这里,如上面所说,因为编译器认为结构体 s 被认为是处于 0x0 地址处,所以 m 的地址自然的就是 m 在 s 中的偏移地址了。
        最后将这个偏移值转化为 size_t 类型。

        可能会感到迷惑,这样强制转换后的结构指针怎么可以用来访问结构体字段?呵呵,其实这个表达式根本没有也不打算访问m字段。ANSIC标准允许任何值为0的常量被强制转换成任何一种类型的指针,并且转换结果是一个NULL指针,因此((s*)0)的结果就是一个类型为s*的NULL指针。如果利用这个NULL指针来访问s的成员当然是非法的,但&(((s*)0)->m)的意图并非想存取s字段内容,而仅仅是计算当结构体实例的首址为((s*)0)时m字段的地址。聪明的编译器根本就不生成访问m的代码,而仅仅是根据s的内存布局和结构体实例首址在编译期计算这个(常量)地址,这样就完全避免了通过NULL指针访问内存的问题。又因为首址的值为0,所以这个地址的值就是字段相对于结构体基址的偏移。
size_t是针对系统定制的一种数据类型。它不是固定位数,在不同的系统里这个值都有可能不同( 它实际上是 unsigned int 类型 );而且在内存里,对于数据是高位对齐存储还是低位对齐存储各系统都不一样。所以,为了提高代码的可移植性,就有必要定议这样的数据类型。一般这种类型都会定义到它具体占几位内存等。当然,有些是编译器或系统已经给定义好的。

 
欺骗编译器的#define offsetof(s,m) (size_t)&(((s *)0)->m)
  在MPC8323上使用到SEC,在文件sec2.h的L58中,涉及这样的语句:
#ifndef offsetof
#define offsetof(s,m)   (size_t)&(((s *)0)->m)
#endif
  琢磨半天也比较头疼那个0的含义,感觉就是转化一个0为s结构,获取成员m的地址。但是,为啥是0呢?为啥呢?
 
  搜索了才发现,原来这个东西也很巧妙。
  (s   *)0   是骗编译器说有一个指向类(或结构)s的指针,其值0。
  &(((s   *)0)->m)   是要取得类s中成员变量m的地址,由于这个类的基址为0,这时m的地址当然就是m在s中的偏移了。
 
  代码巧妙的地方就在于用0来表示这个指针,0成为了这个类/结构的基址,所以成员的地址就成了偏移地址。 或许这也是代码精巧所在吧!


三、container_of宏

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

关于此宏,仔细审读上面的例子,有助于下面的理解。
其中 ptr是指向正被使用的某类型变量指针;type是包含ptr指向的变量类型的结构类型;member是type结构体中的成员,类型与ptr指向的变量类型一样。
功能是计算返回包含ptr指向的变量所在的type类型结构变量的指针。(比较拗口)
该宏的实现思路:计算type结构体成员member在结构体中的偏移量,然后ptr的地址减去这个偏移量,就得出type结构变量的首地址。
该宏的实现方法:
1、通过typeof关键字定义一个与type结构体的member成员相同的类型的变量__mptr且将ptr值赋给它。
        2、用宏offsetof(type,member),获取member成员在type结构中的偏移量
        3、最后将__mptr值减去这个偏移量,就得到这个结构变量的地址了(亦指针)。

typeof是个关键字,可用来引用宏参数的类型。

示例
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/init.h>

  4. struct test{
  5.     int a;
  6.     char b;
  7.     int c;
  8. };

  9. struct test tmp = {
  10.     .= 10,
  11.     .= 20,
  12.     .= 30
  13. };

  14. int *val = &tmp.b;
  15. static __init int init_func(void)
  16. {
  17.      struct test *tst;
  18.      tst = container_of( val, struct test, b );
  19.      printk(KERN_DEBUG"a = %d, b = %d, c = %d\n",tst->a,tst->b,tst->c);
  20.      return 0;
  21. }

  22. static __exit void release_func(void)
  23. {
  24.       printk(KERN_DEBUG"88\n");
  25. }

  26. module_init(init_func);
  27. module_exit(release_func);


参考文章:

[1].Linux/kernel.h中强大的container_of宏

[2].offsetof 详解

[3]. 对container_of(ptr,type,member)分析 



你可能感兴趣的:(程序设计语言,EOS,宏,CC++,Linux)