编程艺术剖析 darknet C 链表实现

Posted 极智视界

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编程艺术剖析 darknet C 链表实现相关的知识,希望对你有一定的参考价值。

欢迎关注我的公众号 [极智视界],获取我的更多笔记分享

O_o>_<o_OO_o~_~o_O

  本文介绍一下 darknet 中链表的实现。

  链表是 C 语言中一种基本数据结构,逻辑上一个个挨着的数据,实际存储却不是一个个挨着的,而是节点数据随机的分布在内存中的各个位置。由于数据是分散存储的,所以在每个节点都需要有个指针指向紧挨着的后面的节点,这样衔接起来串成一条链,最后一个节点的指针指向 NULL,示意图就像这样:

  darknet 是个纯 C 的推理框架,里面有很多地方用到了链表的数据结构,这里从 darknet 框架中的链表操作展开,包括链表实现、查找、插入等基于链表的方法,其中关于链表的声明和实现主要在 lish.h 和 list.c 中,下面进行介绍分析。

文章目录

1、list.h

  直接在代码中注释了:

#ifndef LIST_H
#define LIST_H

/// 定义节点结构体
typedef struct node{
    void *val;                       // 存放当前节点的值,空指针
    struct node *next;               // 下一节点
    struct node *prev;               // 上一节点
} node;

/// 定义链表结构体
typedef struct list{
    int size;                        // 链表长度 
    node *front;                     // 该链表的第一个节点
    node *back;                      // 该链表的最后一个节点
} list;

#ifdef __cplusplus
extern "C" {
#endif
/// 声明链表方法
list *make_list();                   // 初始化链表
int list_find(list *l, void *val);   // 查找链表
void list_insert(list *, void *);    // 插入链表
void **list_to_array(list *l);       // 链表转数组
    
/// 释放空间
void free_list_val(list *l);
void free_list(list *l);
void free_list_contents(list *l);
void free_list_contents_kvp(list *l);

#ifdef __cplusplus
}
#endif
#endif

2、list.c

  list.c 是 list.h 对应的实现部分。要说 list.c 首先需要说一下 xmalloc 和 xcalloc,这两个函数会在 list.c 中用到。

  其实 xmalloc 就是把 malloc 包了一层,看下 xmalloc 的实现:

/// utils.h
#define xmalloc(s) xmalloc_location(s, DARKNET_LOC)

  这个宏会把 xmalloc 用 xmalloc_location 替换,xmalloc_location 的实现如下。可以看到里面最关键的就是一个 malloc 的方法,我们知道 malloc 的作用是在内存的动态存储中分配一块长度为 size 大小的连续存储空间,所以 xmalloc_location 的作用同样如此。

void *xmalloc_location(const size_t size, const char * const filename, const char * const funcname, const int line) {
    void *ptr=malloc(size);
    if(!ptr) {
        malloc_error(size, filename, funcname, line);
    }
    return ptr;
}

  同样来看一下 xcalloc:

/// utils.h
#define xcalloc(m, s) xcalloc_location(m, s, DARKNET_LOC)

  这个宏会把 xcalloc 用 xmalloc_location 替换,xcalloc_location 的实现如下。可以看到里面最关键的就是一个 calloc 的方法,我们知道 calloc 的作用是动态申请 nmemb 个 size 大小的内存空间,并把分配的内存空间全部初始化为零,返回该区域的首地址。

void *xcalloc_location(const size_t nmemb, const size_t size, const char * const filename, const char * const funcname, const int line) {
    void *ptr=calloc(nmemb, size);
    if(!ptr) {
        calloc_error(nmemb *size, filename, funcname, line);
    }
    return ptr;
}

  上面说到了 malloc 和 calloc,两者有一些相似之处,都是用于动态申请内存,但也有明显区别:malloc 不初始化分配的内存,calloc 初始化已分配的内存空间为零。既然都说到 malloc 和 calloc 了,就顺便说一下 realloc,realloc 是将指针 p 所指向的已分配的内存空间大小修改为 size 大小,如下:

(void *)realloc(void *p,unsigned size)

  darknet 中也有关于 realloc 的封装:

void *xrealloc_location(void *ptr, const size_t size, const char * const filename, const char * const funcname, const int line) {
    ptr=realloc(ptr,size);
    if(!ptr) {
        realloc_error(size, filename, funcname, line);
    }
    return ptr;
}

  好了,说完了 malloc、calloc、realloc 后,就可以开始说 lish.c 了,还是直接注释了:

#include <stdlib.h>
#include <string.h>
#include "list.h"
#include "utils.h"
#include "option_list.h"

/// 初始化列表
list *make_list()
{
    list* l = (list*)xmalloc(sizeof(list));           // 开辟链表所需空间
    l->size = 0;                                      // 初始化链表长度
    l->front = 0;                                     // 初始化链表头指针
    l->back = 0;                                      // 初始化链表尾指针
    return l;
}

/*
void transfer_node(list *s, list *d, node *n)
{
    node *prev, *next;
    prev = n->prev;
    next = n->next;
    if(prev) prev->next = next;
    if(next) next->prev = prev;
    --s->size;
    if(s->front == n) s->front = next;
    if(s->back == n) s->back = prev;
}
*/

/// 删除链表尾节点
void *list_pop(list *l){
    if(!l->back) return 0;                           // 判断该链表是否为空
    node *b = l->back;                               // 令 b 指向尾节点
    void *val = b->val;                              // 获取尾节点的值
    l->back = b->prev;                               // 将链接尾节点指向链表倒数第二节点
    if(l->back) l->back->next = 0;                   // 将尾节点的下一节点置为NULL,即删除原链表尾节点
    free(b);                                         // 释放节点b
    --l->size;                                       // 链表长度减1

    return val;                                      // 返回被删除的尾节点的值
}

/// 链表中插入,按顺序插入在尾
void list_insert(list *l, void *val)
{
    node* newnode = (node*)xmalloc(sizeof(node));    // 创建一个节点newnode
    newnode->val = val;                              // 给newnode赋值
    newnode->next = 0;                               // newnode为尾节点,它的下一个节点为NULL

    if(!l->back){                                    // 若链表为空
        l->front = newnode;                          // 则将链表的头节点置为newnode
        newnode->prev = 0;                           // 则该节点的上一节点为NULL
    }else{                                           // 若链表不为空
        l->back->next = newnode;                     // 则链表尾节点的下一个节点即为插入节点
        newnode->prev = l->back;                     // newnode的上一节点为原链表尾节点
    }
    l->back = newnode;                               // 新链表的尾节点为新插入的节点
    ++l->size;                                       // 链表长度加1
}

/// 释放节点存储空间
// 从传入节点n开始,后面的节点依次释放
void free_node(node *n)
{
    node *next;
    while(n) {
        next = n->next;                              // 偏移至下一节点
        free(n);                                     // 释放当前节点
        n = next;                                    // 循环更新
    }
}

/// 释放链表值存储空间
// 从链表头节点开始,依次释放
void free_list_val(list *l)
{
    node *n = l->front;                              // 指向链表头节点
    node *next;
    while (n) {
        next = n->next;                              // 偏移至下一节点
        free(n->val);                                // 释放当前节点值存储空间
        n = next;                                    // 循环更新
    }
}

/// 释放整个链表存储空间
void free_list(list *l)
{
    free_node(l->front);                             // 调用free_node,从头节点开始释放指针域
    free(l);                                         // 释放链表空间
}

/// 和free_list_val功能类似
void free_list_contents(list *l)
{
    node *n = l->front;
    while(n){
        free(n->val);
        n = n->next;
    }
}

/// 释放链表存储空间
// 先来看一下结构体kvp,定义在option_list.h头文件中
/*
typedef struct{
    char *key;
    char *val;
    int used;
} kvp;
*/
void free_list_contents_kvp(list *l)
{
    node *n = l->front;                                  // 指向链表头节点
    while (n) {
        kvp* p = (kvp*)n->val;                           // 此时节点值域为指向kvp结构体的指针
        free(p->key);                                    // 释放kvp内层存储
        free(n->val);                                    // 释放指向kvp指针外层存储
        n = n->next;                                     // 循环更新
    }
}

/// 链表中所有节点的值存数组
void **list_to_array(list *l)
{
    void** a = (void**)xcalloc(l->size, sizeof(void*));   // xcalloc l->size 个void*大小的空间
    int count = 0;
    node *n = l->front;                                   // 指向链表头节点
    while(n){
        a[count++] = n->val;                              // 赋值
        n = n->next;                                      // 循环更新
    }
    return a;
}

  以上剖析分享了 darknet 中关于 C 链表的一些操作方法,也可以帮助温固 C 语言基础知识。


 【公众号传送】

《【编程艺术】剖析 darknet C 链表实现》



扫描下方二维码即可关注我的微信公众号【极智视界】,获取更多AI经验分享,让我们用极致+极客的心态来迎接AI !

以上是关于编程艺术剖析 darknet C 链表实现的主要内容,如果未能解决你的问题,请参考以下文章

编程艺术剖析 darknet read_data_cfg 接口

编程艺术剖析 darknet parse_network_cfg 接口

编程艺术剖析 darknet load_weights 接口

darknet源码剖析 模型训练初探

darknet源码剖析box_iou详细分析

经验分享剖析 darknet entry_index 指针偏移逻辑