如何通过结构体元素找到结构体?

. 问题提出

我们知道,如果有一个结构体定义如下:

 
 
  1. struct _st { 
  2.   int a; 
  3.   char b; 
  4. } st ; 

我们可以通过st访问到a或者b,方法就是st.a(或者如果有st的指针pst,那么就用pst->a)。但是,如果知道了结构体中元素的指针,是否可以获得当前结构体的指针呢?或者说,如果我只能访问到b,我可以访问到st和a么?

2. 这个问题的实际意义

首先,这样做有什么用呢?其实,自己早就知道linux内核中linus就是使用了container_of()的宏,就是利用的这个方法。最近,在项目中,碰到了一个问题,想了想暂时没有想到特别好的方法。而使用这个方法倒是可以比较好的解决问题。

大致上问题抽象出来就是:有一些BLOCK,需要用多个链表串起来。比如有B1,B2,B3,B4,B5共5个BLOCK,要用三个不同功能的链表串起来:

链表1: B1->B4->B5

链表2: B2->B5

链表3: B1->B2->B3->B4->B5

而项目希望使用glib中的链表库实现。而glib中的链表库(这里举一个单向链表的例子)是这样组织的(参见Glib文档):

 
 
  1. typedef struct { 
  2.   gpointer data; 
  3.   GSList *next; 
  4. } GSList;

数据用data指针链接起来,实现单向链表。这里,如果有人有推荐的库也希望说出来,呵呵。这里我们如果用glib的库显然不容易实现我们想要的功能,因为我们不知道B2的next是要指向B5(如链表2中)还是B3(如链表3中)。我们显然需要多个next指针做到这一点。而如果我们这样使用:

 
 
  1. struct _block { 
  2.   int data; 
  3.   GSlist list1; 
  4.   GSlist list2; 
  5.   GSlist list3; 
  6. }; 

因为每一个GSlist的next指针都是指向下一个block的list1(或者list2/3)元素的,我们也难以得到data字段的数据。这时候,就需要通过list1的指针找到data。而这也就是我们提出的问题。

3. 考虑

大致想一下可以知道,如果我们可以知道结构体在内存中存在的方式。比如:

  1. 1. 是否按照定义顺序放置在内存中?
  2. 2. 先定义的元素和后定义的元素,哪个地址在高段?
  3. 3. 是否在元素间有reserved区域用来align?

这样,我们就可以通过加减法计算出pa所指向的这个整形a所属于的结构体的地址。而以上的所有,编译器是肯定知道的,但是我们又怎么在代码中体现呢?

其实,为了用代码表示出来,我们根本不用知道上面的问题。看下面的方法一段便知道了。

4. container_of()的分析

 既然之前知道在linux内核中,linus使用了container_of()这样tricky的宏,来通过一个结构体中元素的指针获得当前结构体的指针,这里,直接拿过来用其实就好了。

container_of()的实现方法很简单,也很巧妙。其核心就是两个宏定义:

 
 
  1. #ifndef offsetof 
  2. #define offsetof(type, field)   ((long) &((type *)0)->field) 
  3. #endif   /* offsetof */ 
  4.  
  5. #ifndef container_of 
  6. #define container_of(ptr, type, member) ({          \ 
  7.     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 
  8.     (type *)( (char *)__mptr - offsetof(type,member) );}) 
  9. #endif 

这里主要有三个宏:typeof(), offsetof(), container_of()。我谈谈我的一些理解:

  1. 1. typeof()可以得到一个变量的类型,这个是编译器要支持的。如我们可以这样写代码:{int a; typeof(a) b;}这段代码等价于:{int a; int b;}
  2. 2. offsetof()利用((type *)0)->field方式得到偏移地址,是假设有一个类型为type的结构在内存的0x0000000处,那么这个结构中field的地址的值就是field字段在结构中的偏移地址了!
  3. 3. container_of()首先定义了一个type.member同样类型的指针__mptr,并将ptr赋值给__mptr,这里其实是为了检验ptr的类型是否是type.member,增加了安全性(比如,当我随便传一个变量的地址ptr给这个宏后,加了校验的这个container_of()就会在那行报错,类型不匹配,如果是强制转换,编译或许是通过了,但是关键时刻一跑估计就是run time error了...);然后用__mptr的地址值减去member在type结构中的偏移地址,得到原始ptr所在结构的结构体地址。
  4. 4. 这里还要注意一个括号的使用方法:({...;...;...;}),我没有对这个的使用在编译器的手册中进行查找,但是大致的理解是,在{}中可以按照平时的习惯写程序,最后({})中可以看作一个值,它的大小是{}中最后一个语句的值。感觉有些类似于(...,...,...)的写法,但是只有()时不能有运算的语句。这里我写了一个简单的例子,输出是两个3:
 
 
  1. int main(void
  2.     printf("%d.\n", (1,2,3)); 
  3.     printf("%d.\n", ({int i=1,j;j=i+2;})); 
  4.     return 0; 

5. container_of()的使用例子

这里我举一个简单的使用container_of()的例子:

 
 
  1. /*
  2. * desc : a simple example to use container_of() macro
  3. * mail : xzpeter@gmail.com
  4. */
  5. #include <stdio.h> 
  6.  
  7. #ifndef offsetof 
  8. #define offsetof(type, field)   ((long) &((type *)0)->field) 
  9. #endif   /* offsetof */ 
  10.  
  11. #ifndef container_of 
  12. #define container_of(ptr, type, member) ({          \ 
  13.     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ 
  14.     (type *)( (char *)__mptr - offsetof(type,member) );}) 
  15. #endif 
  16.  
  17. typedef struct _A { 
  18.     int a; 
  19.     char b; 
  20.     long long c; 
  21.     double d; 
  22. } SA ; 
  23.  
  24. SA sa = { 
  25.     .a = 10, 
  26.     .b = 'c'
  27.     .c = 204, 
  28.     .d = 3.14, 
  29. }; 
  30.  
  31. int main(int argc,char *argv[]) 
  32.     double *pd = &sa.d; 
  33.     /* now we have a pointer pd -> sa.d, let's access element c with pd */ 
  34.     printf("SA.c = %lld\n", container_of(pd, SA, d)->c); 
  35.     return 0; 

这里,我定义了一个结构sa,并通过sa.d的指针得到sa.c的值。输出是:

 
 
  1. SA.c = 204 

这样,我们自然也可以通过block.next得到block.data了。

参考资料:

  1. 1. ”对linux内核代码的一点疑惑:container_of的冗余?“, http://hi.baidu.com/joec3/blog/item/37b0c8900e397487a977a493.html,(其实在开始写这篇文章的时候,没有弄清楚为何要专门要用__mptr这个const指针,在这里找到的答案。同时,还有一篇和我这里分析container_of()很像的文章,都是从三个宏的角度。放到参考2里吧,呵呵)
  2. 2. “对linux内核中container_of宏的理解“, http://hi.baidu.com/tim_bi/blog/item/fdc3d81358e1f60b5aaf53c6.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值