【C++学习笔记】纪录一下最近一次debug错误调试内容

背景:

        我在准备毕业设计项目的时候,设计了一个内存池用来存放数据包内容,然后再实际运行时发现当接收的数据包达到内存池设定的最大上限后会发生错误,紧接着就是漫长的debug时间...

        最终在经过了我长达1小时的debug和函数堆栈查看后,终于找到了问题所在,接下来是问题复原。

错误场景:

        在程序启动后,当数据包分配达到上限后,STL容器std::list的析构函数报错,具体如下图所示:

初步猜想是由于内存池的内存分配问题导致,在分配数量达到上限后,便会引发该异常。于是查找调用堆栈中的对应分配内存池的相关函数,由于我是在我的recv_thread线程中调用的,于是在其中找到了对应的函数,如图所示:

来到对应的函数堆栈后,查看本地的数据,this指针表示我的数据包管理器对象,通过查看其中的内存池类(pkt_mblock 和 pktblk_mblock),发现其中的空闲内存大小都为4,也就是说内存池中的内存还没有被分配完,也就不是内存池的问题了。

排除了内存池的问题后,接着翻看函数堆栈,发现报错发生在第90行中。

我这里的try catch是为了处理内存池分配内存不够的情况,捕获的异常应该是内存不够而抛出的异常,这里的话 等于号右边的函数签名为:

DeList<Packet_Blk*> Core_PktHandler::pkt_blklist_alloc(int size, bool isHeadInsert);

也就是说这里的pkt_blklist_alloc会返回一个DeList对象,然后经由移动语义后传递给我要分配的pkt->blk_list的成员,完成对其的初始化操作。

于是再次查看函数堆栈向上一层的函数,却发现并没有进入到pkt_blklist_alloc函数中,而是进入到了我封装的DeList类的析构函数中。(如下图,绿色的部分表示当前执行到的语句)

我的DeList类中仅含有一个std::list的STL成员,也就是说在该语句结束后,即~Delist()结束后会立马调用list的析构函数,而正是在list的析构函数中程序错误,出现了异常。
                

        那么问题来了,STL容器自带的list的析构函数怎么会抛出异常呢?

排查原因:

        首先我认为是在我的DeList类中的移动构造函数出现了问题,我的移动构造函数看起来像这样:

template <typename T>
DeList<T>::DeList(DeList<T>&& other) noexcept : list(std::move(other.list))
{
}

template <typename T>
DeList<T>& DeList<T>::operator=(DeList<T>&& other) noexcept
{
    if (this != &other)
    {
        swap(list, other.list);
    }
    return *this;
}

        接着通过函数堆栈和日志打印,发现我的移动构造函数确实没有问题,反而是发现了我刚刚写的pkt_blklist_alloc函数存在问题,每次都会额外构造一个对象,然后使用两次移动构造函数才能完成pkt->blk_list的初始化,(这一点我后面修好了),但是这并不影响程序运行,只是效率的问题,也与STL容器的析构函数抛出异常没有关系。        

        于是没有办法,我又接着查看函数堆栈,向下查找。

        终于过了许久,我发现了问题所在,在发生异常的函数中,我发现我的list容器中的数据段指向的位置都为0x000000000000,也就是只读字段,如下图:

也就是说,STL容器中的std::list在析构时,尝试将其中的数据进行清空(这些字段什么意思我也不清楚),但是发现了其指向只读字段,于是发生异常:

发生异常: W32/0xC0000005

Unhandled exception thrown: read access violation. _Head was nullptr.

那么为什么在这里的值即为空,在其他地方调用析构函数却能正常运行呢?

找到原因:

·        于是我结合整个项目,发现了问题所在:

        原来在我的内存池类中,当调用free函数释放资源时,我会调用memset将相关字段设置为0,这本来是帮助防止访问冲突的,结果在这里却清空了Packet的blk_list对象。

        由于我的内存池类是在管理已经构造好的Packet数组,在其中分配和释放Packet的内存,所以这里的memset相当于清空了Packet类的实例当中的原有的pkt_list对象,使其为0。

        接着在析构函数中,实际为0的pkt_list被移动构造函数 交换给临时量后,临时量调用析构函数清空资源,发现自己的数据字段异常,产生了一开始的错误。

        至于为什么在达到内存池管理上限才会发生错误,原因是我的内存池设计时为循环分配内存,最开始的内存在循环一轮,其他所有内存都被分配以后才会被分配,也就是说,如果我的内存池大小为20,那么在分配第21次时,由于前面20次调用free时,清空了每个构造好的pkt_list对象,在第21次分配时,就会使用错误的std::list对象,从而引发异常。

我的感悟:

        写c艹真爽!!!哈哈哈哈哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值