ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

双向链表的宏实现——解析shttpd的链表实现

2021-05-17 10:53:17  阅读:184  来源: 互联网

标签:prev struct 实现 LL next 链表 llhead shttpd define


shttpd是一个开源的跨平台的轻量级web服务框架,源码下载地址。这里是使用的1.42版本。

shttpd中双向链表的功能和内核双向链表一致,都是定义一个存放前后指针的节点,将节点存放到某个结构体中,该结构体中的节点关联起来,然后就可以通过链表的方式操作该结构体对象了。

节点结构:
struct llhead 为内核双向链表的节点,定义如下:

struct llhead {
    struct llhead   *prev;
    struct llhead   *next;
};

链表的定义:

#define LL_HEAD(H)  struct llhead H = { &H, &H }
//例如定义一个静态的llhead节点
static LL_HEAD(listeners);

链表的初始化:
链表初始化,用来将节点的前向、后向指针指向自身。定义及使用如下:

#define LL_INIT(N)  ((N)->next = (N)->prev = (N))
struct llhead   listeners;
LL_INIT(&listeners);

添加节点:
向后添加节点

#define LL_ADD(H, N)                            \
    do {                                        \
        ((H)->next)->prev = (N);                \
        (N)->next = ((H)->next);                \
        (N)->prev = (H);                        \
        (H)->next = (N);                        \
    } while (0)
//使用:
struct sockInfo{
    int sockfd;
    int family;
    int port;
    in_addr addr;
    LL_HEAD(listeners);
};
// 将2个sockInfo用链表连接起来
struct sockInfo si1;
struct sockInfo si2;
LL_INIT(&si1.listeners);
LL_INIT(&si2.listeners);
LL_ADD(&si1.listeners, &si2.listeners); 

双向链表插入示意

删除节点:
将某个节点从链表中删除(让该链表的下一节点与上一节点相互指向对方),并再次初始化该节点

#define LL_DEL(N)                           \
    do {                                \
        ((N)->next)->prev = ((N)->prev);            \
        ((N)->prev)->next = ((N)->next);            \
        LL_INIT(N);                     \
    } while (0)

遍历节点:
LL_FOREACH通常用于获取节点,而不能用到删除节点的场景
LL_FOREACH_SAFE通常删除节点的场景
此实现貌似不能遍历H自身~

#define LL_FOREACH(H,N) for (N = (H)->next; N != (H); N = (N)->next)
#define LL_FOREACH_SAFE(H,N,T)                      \
    for (N = (H)->next, T = (N)->next; N != (H);N = (T), T = (N)->next)

// 使用for_each遍历struct llhead *head
struct llhead     *lp, *tmp;
LL_FOREACH_SAFE(head, lp, tmp) {
    LL_DEL(lp);
}

链表判空
如果链表指向自身,则为空

#define LL_EMPTY(N) ((N)->next == (N))

向链表末尾添加节点

#define LL_TAIL(H, N)                           \
    do {                                         \
        ((H)->prev)->next = (N);                \
        (N)->prev = ((H)->prev);                \
        (N)->next = (H);                        \
        (H)->prev = (N);                        \
    } while (0)

获取结构体变量指针:
根据”结构体(T)变量中的域成员变量N的指针来获取指向整个结构体变量的指针。

#define LL_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))
// 示例
struct worker {
    struct llhead    link;
    struct llhead   connections;    /* List of connections      */
};
struct llhead *lp = &pworker->link;
struct worker   *worker = LL_ENTRY(lp, struct worker, link);

说明:
offsetof用来获取T类型中N的相对偏移。假设link相对于worker的偏移为offset,当获取到link内存中的地址link_ptr时,就可以通过可以获取到pworker所指向的内存。此时pworker = link_prt - offset;也就是LL_ENTRY的实现。

offsetof实现:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

假设存在结构体struct TYPE如下
struct TYPE{
T t1;
//…
P p1;
};
当定义变量:struct TYPE tp1;其地址为&tp1,其成员变量t1的地址为&(tp1.t1)
则如下代码可以取到p1在内存中的地址以及相对于tp1的位移

struct TYPE tp1;
struct TYPE *ptp = &tp1;
int addr_t1 = &ptp->t1;// t1在内存中的地址
int addr_p1 = &ptp->p1;// p1内存中的地址
// p1相对于tp的内存偏移为
size_t offset = (char *)&tp1->p1 - (char *)tp1;

当ptp指向null指针时,offsetof =(size_t) ((char *)&0->t1 ;

//list.h
/*
 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
 * All rights reserved
 *
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Sergey Lyubka wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.
 */

#ifndef LLIST_HEADER_INCLUDED
#define LLIST_HEADER_INCLUDED

/*
 * Linked list macros.
 */
struct llhead {
    struct llhead   *prev;
    struct llhead   *next;
};

#define LL_INIT(N)  ((N)->next = (N)->prev = (N))

#define LL_HEAD(H)  struct llhead H = { &H, &H }

#define LL_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))

#define LL_ADD(H, N)                            \
    do {                                \
        ((H)->next)->prev = (N);                \
        (N)->next = ((H)->next);                \
        (N)->prev = (H);                    \
        (H)->next = (N);                    \
    } while (0)

#define LL_TAIL(H, N)                           \
    do {                                \
        ((H)->prev)->next = (N);                \
        (N)->prev = ((H)->prev);                \
        (N)->next = (H);                    \
        (H)->prev = (N);                    \
    } while (0)

#define LL_DEL(N)                           \
    do {                                \
        ((N)->next)->prev = ((N)->prev);            \
        ((N)->prev)->next = ((N)->next);            \
        LL_INIT(N);                     \
    } while (0)

#define LL_EMPTY(N) ((N)->next == (N))

#define LL_FOREACH(H,N) for (N = (H)->next; N != (H); N = (N)->next)

#define LL_FOREACH_SAFE(H,N,T)                      \
    for (N = (H)->next, T = (N)->next; N != (H);            \
            N = (T), T = (N)->next)

#endif /* LLIST_HEADER_INCLUDED */

标签:prev,struct,实现,LL,next,链表,llhead,shttpd,define
来源: https://blog.51cto.com/u_6650004/2780458

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有