访问链表list释放后内存(野指针):两次coredump排除经历(一)重复添加节点

问题

最近维护一套祖传代码,测试时,在遍历hash表时,概率产生了coredump。 直接原因是访问hash冲突链上的node是个非法指针。很自然的想法,是不是删除节点的时候遗漏摘除链表,导致是野指针残留在hash表上?请 2分钟 想一想还有哪些可能。

先来回顾下hash表的基础知识。

1、hash表简述

链表的结构

hash表实现千差万别,常见的是基于数组拉链法。如下图,hash桶是个数组,冲突链用双向链表实现。

双向链表

在链表中,每一个结点的结构都包括了两部分的内容:数据域和指针域

arduino 复制代码
/* Doubly linked list head or element. */
struct list_node {
    struct list_node *prev;     /* Previous list element. */
    struct list_node *next;     /* Next list element. */
};
/*  业务数据 */
struct user_data_t {
    struct list node;     /* 用来存在全局hash表的节点 */
    char name[32];       /* user data. */
    ......
};

链表遍历

查找hash节点时,首先根据hash key 计算hash index, 即可以定位到数组下表。其次遍历hash冲突链。 如果在遍历的过程中节点可能有增删(其他线程),需采用安全遍历的宏

scss 复制代码
/* 链表头 */
struct list_head{
    list_node* pstFirst; /* the first element */
} ;

#define LIST_FOREACH_SAFE_RCU(pstList, pstNode, pstNextNode) \
    for ((pstNode) = rcu_dereference((pstList)->pstFirst); \
        (null != (pstNode)) && ({(pstNextNode) = rcu_dereference((pstNode)->pstNext); true;}); \
        (pstNode) = (pstNextNode))

回顾了基本的数据结构后,再来看下hash表上残留释放后内存的几种可能。

2、问题分析

释放后内存

分析coredump文件,如下图,变量hash冲突链 3 时,A,B节点均正常。C节点查看内存头已经不对。但也能正确访问,根据C节点的获取下一个节点D(D = C->next),却不是一个有效指针,直接报非法地址错误,产生core.

原因分析

1、释放节点未摘除链表

如果存在释放内存,却没有摘除节点,问题是必现流程。 实际排除代码,所有free内存的地方都删除了节点。链表节点删除代码如下:

rust 复制代码
void list_remove(struct list_node *node){
    node->prev->next = node->next;
    node->next->prev = node->prev;
    return;
}

2、踩内存

场景一:当前业务申请的C节点内存,是第三者申请和释放后内存、但是第三者继续持有。第三者异常写操作写坏内存的next域 和 内存头。 这种场景还有个特点,因为第三者这个内存可以被不同业务随机申请走,会有一些其他异常现象。

场景二:当前内存为C节点持有,但是有其他业务飞踩,踩到C节点的next域。比如越界写,写到C内存区域。一般这种场景也有个特点,C的整个内存区域可能都不对。不太会是next(8字节)不对,其他都是正常数据。

一般踩内存,是定位问题最后怀疑的方向。踩内存出现的异常,都不是第一现场,定位难度就大多了。

结合问题本身:查看了C的整个内存区域,其他数据域内容没有明显异常。内存头也是个释放后内存特征。且概率复现了几次,都是该业务节点。 所以踩内存的概率就很低。

3、链表重复添加

该问题 最终定位原因就是同一个节点C被重复添加到hash表中。第二次添加节点时,没有检测是否已经存在。

步骤1: 正常添加节点,如上图有A--B--C--D四个节点

步骤2: 再次添加节点C,采用链表头插法。数据结构就变成下面。

有两个问题:1、B的下一个节点是C,成环了。如果遍历会出现死循环。2、D变成了游离节点。

步骤3: 再次把C节点删除和释放。B的next还记录着C节点,就是个释放后内存。就有可能业务拿到C节点内存正常写内容。就会出现异常。

总结

链表作为最简单的数据结构,如果不能正确使用,遇到坑问题就会很难定位。

上面问题解决后,又有另外一个类似问题。 遍历list时访问异常指针,coredump文件,却显示冲突链找不到该节点。当前冲突链,只有两个节点,比如A,B,B的next指针是null。 下一篇继续分析。

相关推荐
新晓·故知24 分钟前
<基于递归实现线索二叉树的构造及遍历算法探讨>
数据结构·经验分享·笔记·算法·链表
总裁余(余登武)35 分钟前
算法竞赛(Python)-万变中的不变“随机算法”
开发语言·python·算法
Eric.Lee20211 小时前
音频文件重采样 - python 实现
人工智能·python·深度学习·算法·audio·音频重采样
huapiaoy1 小时前
Redis中数据类型的使用(hash和list)
redis·算法·哈希算法
冷白白1 小时前
【C++】C++对象初探及友元
c语言·开发语言·c++·算法
鹤上听雷2 小时前
【AGC005D】~K Perm Counting(计数抽象成图)
算法
一叶祇秋2 小时前
Leetcode - 周赛417
算法·leetcode·职场和发展
武昌库里写JAVA2 小时前
【Java】Java面试题笔试
c语言·开发语言·数据结构·算法·二维数组
ya888g2 小时前
GESP C++四级样题卷
java·c++·算法
Funny_AI_LAB2 小时前
MetaAI最新开源Llama3.2亮点及使用指南
算法·计算机视觉·语言模型·llama·facebook