我们知道,如果有一个结构体定义如下:
- struct _st {
- int a;
- char b;
- } st ;
我们可以通过st访问到a或者b,方法就是st.a(或者如果有st的指针pst,那么就用pst->a)。但是,如果知道了结构体中元素的指针,是否可以获得当前结构体的指针呢?或者说,如果我只能访问到b,我可以访问到st和a么?
首先,这样做有什么用呢?其实,自己早就知道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文档):
- typedef struct {
- gpointer data;
- GSList *next;
- } GSList;
数据用data指针链接起来,实现单向链表。这里,如果有人有推荐的库也希望说出来,呵呵。这里我们如果用glib的库显然不容易实现我们想要的功能,因为我们不知道B2的next是要指向B5(如链表2中)还是B3(如链表3中)。我们显然需要多个next指针做到这一点。而如果我们这样使用:
- struct _block {
- int data;
- GSlist list1;
- GSlist list2;
- GSlist list3;
- };
因为每一个GSlist的next指针都是指向下一个block的list1(或者list2/3)元素的,我们也难以得到data字段的数据。这时候,就需要通过list1的指针找到data。而这也就是我们提出的问题。
大致想一下可以知道,如果我们可以知道结构体在内存中存在的方式。比如:
这样,我们就可以通过加减法计算出pa所指向的这个整形a所属于的结构体的地址。而以上的所有,编译器是肯定知道的,但是我们又怎么在代码中体现呢?
其实,为了用代码表示出来,我们根本不用知道上面的问题。看下面的方法一段便知道了。
既然之前知道在linux内核中,linus使用了container_of()这样tricky的宏,来通过一个结构体中元素的指针获得当前结构体的指针,这里,直接拿过来用其实就好了。
container_of()的实现方法很简单,也很巧妙。其核心就是两个宏定义:
- #ifndef offsetof
- #define offsetof(type, field) ((long) &((type *)0)->field)
- #endif /* offsetof */
- #ifndef container_of
- #define container_of(ptr, type, member) ({ \
- const typeof( ((type *)0)->member ) *__mptr = (ptr); \
- (type *)( (char *)__mptr - offsetof(type,member) );})
- #endif
这里主要有三个宏:typeof(), offsetof(), container_of()。我谈谈我的一些理解:
- int main(void)
- {
- printf("%d.\n", (1,2,3));
- printf("%d.\n", ({int i=1,j;j=i+2;}));
- return 0;
- }
这里我举一个简单的使用container_of()的例子:
- /*
- * desc : a simple example to use container_of() macro
- * mail : [email protected]
- */
- #include <stdio.h>
- #ifndef offsetof
- #define offsetof(type, field) ((long) &((type *)0)->field)
- #endif /* offsetof */
- #ifndef container_of
- #define container_of(ptr, type, member) ({ \
- const typeof( ((type *)0)->member ) *__mptr = (ptr); \
- (type *)( (char *)__mptr - offsetof(type,member) );})
- #endif
- typedef struct _A {
- int a;
- char b;
- long long c;
- double d;
- } SA ;
- SA sa = {
- .a = 10,
- .b = 'c',
- .c = 204,
- .d = 3.14,
- };
- int main(int argc,char *argv[])
- {
- double *pd = &sa.d;
- /* now we have a pointer pd -> sa.d, let's access element c with pd */
- printf("SA.c = %lld\n", container_of(pd, SA, d)->c);
- return 0;
- }
这里,我定义了一个结构sa,并通过sa.d的指针得到sa.c的值。输出是:
- SA.c = 204
这样,我们自然也可以通过block.next得到block.data了。