LIST_ENTRY结构

在Windows驱动相关编程中,会用到该结构。Windows的源代码中大量使用了该结构。该结构用来组成常见的数据结构——双链表,并且带有头部节点。带头部节点的链表相对于不带头部节点的链表简化了一些链表操作,主要是插入和删除。

LIST_ENTRY结构如下:

typedef struct _LIST_ENTRY {

  struct _LIST_ENTRY  *Flink;

  struct _LIST_ENTRY  *Blink;

} LIST_ENTRY, *PLIST_ENTRY;

这是一个最简单的双链表节点,只保存了下一个节点指针Flink(Follow Link)和前一个节点指针Blink(Before Link)。我们自己使用链表时会在声明时加入数据域,同时指针域为我们自己的节点类型,例如:

typedef struct _NODE {

    int data; // 数据域

    struct _NODE *Flink; // 后节点指针

    struct _NODE *Blink; // 前节点指针

}NODE;

两者一对比,就让我们产生一个疑问,LIST_ENTRY如何加入数据域?

这也是LIST_ENTRY设计的巧妙处所在。LIST_ENTRY结构不单独使用,而是在声明其它结构时,将其作为要组成双链表的结构体的一个成员。例如:

typedef struct _NODE {

    int data1; // 数据域1

    LIST_ENTRY ListEntry;

    int data2; // 数据域2

}NODE;

这样,通过ListEntry成员可以将结构体连接为一个双链表,如下图所示:

LIST_ENTRY结构_第1张图片

头结点不带数据域,为LIST_ENTRY结构,使用前要用InitializeListHead函数初始化。

下面还有一个问题,我们通过LIST_ENTRY结构结构串联起来,使用时通过前后指针获取得到的都是LIST_ENTRY结构本身,即NODE结构中的成员ListEntry。而实际使用时要得到的是NODE结构本身的指针,这也就是招聘笔试面试过程中常常遇到的一个问题:知道结构体中某个成员的地址,如何获取结构体的起始地址?

讲如何做之前,先看看Windows中怎么获取。就是使用CONTAINING_RECORD宏,这个宏的原型如下:

PCHAR CONTAINING_RECORD(

  [in] PCHAR Address,

  [in] TYPE  Type,

  [in] PCHAR Field

);

其中,Type为结构体类型,FieldType结构中的已知成员,AddressField成员的已知地址,宏的结果为Type结构的起始地址。对于我们的链表示例,获取链表第一个NODE结构的起始地址用法为:

CONTAINING_RECORD(ListHead.Flink,NODE,ListEntry)

具体的实现原理比较简单,知道成员的地址,算结构体起始地址,只需要知道成员的偏移,因此,将结构体起始对齐到地址0,然后获取成员的地址,即为成员的偏移量,然后减去偏移量即可,该宏可如下实现:

((Type *)(((ULONG)Address) - (ULONG)(&(((Type *)0)->Field))))

你可能感兴趣的:(数据结构,windows,链表)