linux下链表实现的一些思考

看过linux的链表实现的方法,都知道用的很巧妙,尤其是list_entry宏的定义更是充分运用了C语言指针的灵活性。下面是具体的宏定义

list_entry()宏

#define list_entry(ptr, type, member) \
        ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
     ptr是指向list_head类型链表的指针,type为一个结构,而member为结构type中的一个域,类型为list_head,这个宏返回指 向type结构的指针。在内核代码中大量引用了这个宏,因此,搞清楚这个宏的含义和用法非常重要。

   
设有如下结构体定义:
typedef struct xxx
{

     ……//其他一些字段

    .........

     type1 member;
     ……
}type;

定义变量:
   type a;
   type * b;
   type1 * ptr;
执行:
   ptr=&(a.member);
   b=list_entry(ptr,type,member);
则可使b指向a,得到了a的地址。

如何做到的呢?

先看&((type *)0)->member:
把“0”强制转化为指针类型,则该指针一定指向“0”(数据段基址)。因为指针是“type *”型的,所以可取到以“0”为基地址的一个type型变量member域的地址。那么这个地址也就等于member域到结构体基地址的偏移字节数。再来看 ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))):(char *)(ptr)使得指针的加减操作步长为一字节,不然加减步长就是sizeof(type),(unsigned long)(&((type *)0)->member)等于ptr指向的member到该member所在结构体基地址的偏移字节数。做减法就能得到结构体实例变量的首地址。

以上是随地可以百度到的解释,我的疑问是(char *)(ptr)这个地方的指针转换,装换成(char*)理所当然是为了使得指针偏移步长变为1字节,当这个地放后面的member偏移地址转换为unsigned long 我觉得把ptr也转换为unsigned long型更加有利于理解,这样使得前后格式一致完全转换为地址的加减运算,绕过指针偏移这一层面,对于代码调试如下


[cpp]  view plain copy
  1. #include "stdafx.h"  
  2. using namespace std;  
  3. typedef struct list    
  4. {    
  5.      struct list *next;    
  6.      struct list *prev;    
  7. }list;    
  8.   
  9. typedef struct type  
  10. {  
  11. int a;  
  12. char b;  
  13. list member;  
  14. }type;  
  15. list* ptr;  
  16. int _tmain(int argc, _TCHAR* argv[])  
  17. {  
  18.     type node;  
  19.     type *pp;  
  20.     ptr = &(node.member);  
  21.     node.a=4444;  
  22.     node.b='b';  
  23.     pp = ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)));  
  24.     //cout<<((type *)((unsigned long)(ptr)-(unsigned long)(&((type *)0)->member)));  
  25.     cout<<pp<<endl<<pp->a<<endl<<pp->b<<endl;  
  26.     return 0;  
  27. }  
个人觉得以下定义是不是更好一下
#define list_entry(ptr, type, member) \
        ((type *)((unsigned long)ptr-(unsigned long)(&((type *)0)->member)))

你可能感兴趣的:(链表)