问题描述

今天写项目的时候遇见一个特别诡异的 bug,体现在在执行某条语句时,程序会莫名崩溃,并且给出的错误信息也非常难懂,只有一个malloc(): invalid size (unsorted)错误信息,从直观上看起来是 malloc 函数无法分配到内存,就想着应该是哪个动态分配内存的地方变量没获取到值,但是调试的时候才发现没这么简单。

问题排查

调试的时候,发现程序崩溃的时候的调用栈最后竟然是一个 vector,并且是在 push_back 的时候,心里面就隐隐感觉不对了,因为这个程序中的数据远远达不到内存超限的地步,而 vector 的内存是动态分配的,所以说基本上不可能获取不到足够的内存。

看源码时注意到另一个 vector 就不会崩溃,于是就增加几个类型的 vector,逐一试验,发现在基本数据类型中,std::int32_t,std::int64_t 和他对应的无符号类型就不会导致程序崩溃,但是 std::int16_t,std::int8_t,bool 和 char 就会导致程序崩溃,分析到这里,看起来好像是大于等于 32 个字节的数据类型就不会崩溃。但是,我自定义的一个 struct 也会导致崩溃,而这个 struct 有 48 个字节,调试到这,感觉这个 bug 越来越诡异了。

按理来说,在 C++ 里面,普通的结构体如果没有虚函数的话和自带的数据类型是完全相同的,都是一个内存地址,对应着其大小的字节流,但是在这,不同大小的类型竟然有不同的反应。

于是就调试到 STL 的源码,发现最后一个调用的语句是::operator new(),这个是一个按照字节分配内存的语句,语句把语句单独拿出来,放到崩溃语句的前面,发现程序的确会直接报malloc(): invalid size (unsorted)错误,但如果放在 main 函数最前面的话,却不会崩溃,最后反复定位,定位到最终会引起 bug 的地方。这个函数如下:

storage::SQLBinaryData Pager::readRow(std::uint32_t pos) {
if (pos <= getFileSize()) {
std::uint32_t size;
dataFile.seekg(pos, dataFile.beg);
dataFile.read((char *) &size, sizeof(size));
// data 里面是一个 new 出来的 char 数组 的 shared_ptr
SQLBinaryData data(size); auto addr = data.data.get();
dataFile.read(addr, size); // 这就是能造成崩溃的 ::operator new 语句
auto test = ::operator new(1);
return data;
} else {
spdlog::error("read file out of file size");
return SQLBinaryData(0);
}
}

解决方案

可以看出,这个 bug 大概率和 shared_ptr 有关,在网上查阅了很长时间资料,最后才知道在 C++17 之前,shared_ptr 并不支持动态数组,在析构的时候 shared_ptr 只会调用 delete,而不是 delete[],如果要管理 new[]构造出来的数组,需要在构造的时候传入自定义的 delete 删除器 std::default_delete,要么就使用 unique_ptr。

其实大部分情况下智能指针并不需要 shared_ptr,用 unique_ptr 就够了,没有这么多要共享的东西。

还有一种比较简便的做法,就是直接用 vector 来管理动态数组,这就已经能满足很多 new[] 的情况了。一般情况下,写 C++ 的时候,还是得遵循能不用指针就不用指针的原则。

记一个非常诡异的关于 shared_ptr 的 bug的更多相关文章

  1. 记一个关于std::unordered_map并发访问的BUG

    前言 刷题刷得头疼,水篇blog.这个BUG是我大约一个月前,在做15445实现lock_manager的时候遇到的一个很恶劣但很愚蠢的BUG,排查 + 摸鱼大概花了我三天的时间,根本原因是我在使用s ...

  2. 记一个社交APP的开发过程——基础架构选型(转自一位大哥)

    记一个社交APP的开发过程——基础架构选型 目录[-] 基本产品形态 技术选型 最近两周在忙于开发一个社交App,因为之前做过一点儿社交方面的东西,就被拉去做API后端了,一个人头一次完整的去搭这么一 ...

  3. 面试官问,说一个你在工作非常有价值的bug

    如果你去参考面试,做足了准备,面对面试官员从容不迫,吐沫横飞的大谈自己的工作经历.突然,面试官横插一句:说一个你在工作非常有价值的bug.顿时,整个空气都仿佛都凝固了!“What?”... 我想没几个 ...

  4. 解Bug之路-记一次JVM堆外内存泄露Bug的查找

    解Bug之路-记一次JVM堆外内存泄露Bug的查找 前言 JVM的堆外内存泄露的定位一直是个比较棘手的问题.此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤 ...

  5. Java中,一个存在了十几年的bug...

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  6. 记一个奇怪的python异常处理过程

    我的一个程序, 总是在退出时报异常, Exception TypeError: "'NoneType' object is not callable" in <functio ...

  7. 记一个常见的ms sql server中取第N条记录的方法

    前言 好好学习,天天向上. 正文 好像也是一个不难的问题,刚视频里看到的,就记一下吧. 下面是表中原始的数据结构,做了一个倒叙排序: select * from Employee order by S ...

  8. 记一个mvn奇怪错误: Archive for required library: 'D:/mvn/repos/junit/junit/3.8.1/junit-3.8.1.jar' in project 'xxx' cannot be read or is not a valid ZIP file

    我的maven 项目有一个红色感叹号, 而且Problems 存在 errors : Description Resource Path Location Type Archive for requi ...

  9. 记一个简单的sql查询

    在我们做各类统计和各类报表的时候,会有各种各样的查询要求.条件 这篇主要记录一个常见的统计查询 要求如下: 统计一段时间内,每天注册人数,如果某天没有人注册则显示为0 现在建个简单的表来试试 建表语句 ...

随机推荐

  1. python实现遥感图像阈值分割

    1.阈值分割 import os import cv2 import numpy as np import matplotlib.pyplot as plt from osgeo import gda ...

  2. SAR总结

    1.星载InSAR技术简介 星载合成孔径雷达干涉测量(InSAR)是一种用于大地测量和遥感的雷达技术.InSAR使用两个或多个SAR图像,利用返回卫星的波的相位差来计算目标地区的地形.地貌以及表面的微 ...

  3. selenium-ide-2.3.0 组件在foxfire45.0无法安装的问题

    楼主在安装selenium-ide组件时,尝试了下面两种方式都无法安装: 1.在forfire浏览器进行拖拽安装,页面无任何跳转.拖拽后回车安装,也没任何效果 2.附件组件-从文件安装添加组件,添加了 ...

  4. HDFS基本命令

    1.创建目录 hadoop dfs -mkdir /data hadoop dfs -mkdir -p /data/data1   创建多级目录 2.查看文件 hadoop dfs -ls / 3.上 ...

  5. sql语句异常向数据库插入数据报错

    在php编程向数据库插入数据时报如下错误: [Err] 1064 - You have an error in your SQL syntax; check the manual that corre ...

  6. 关于 CLAHE 的理解及实现

    CLAHE CLAHE 是一种非常有效的直方图均衡算法, 目前网上已经有很多文章进行了说明, 这里说一下自己的理解. CLAHE是怎么来的 直方图均衡是一种简单快速的图像增强方法, 其原理和实现过程以 ...

  7. Hyper-V + WSL2与 VirtualBox 共存

    Hyper-V + WSL2与 VirtualBox 共存 这样的教程网上有很多,我先简单复述一下.真正麻烦的是我遇到的问题--开启 Hyper-V 后我的电脑会多出几个删不掉的虚拟显示器来,会在文章 ...

  8. 433MHZ SPI模块使用心得

    最近使用了433MHZ的模块进行了一个通讯项目,选用的是SX1208模块,对接了RTOS和Linux两个操作系统,使用心得如下: 1. 首先要拿来datasheet看一遍,通揽一下它的功能.可以得到一 ...

  9. PHP获取目录中的全部内容RecursiveDirectoryIterator

    这次我们来介绍一个SPL库中的目录迭代器,它的作用其实非常简单,从名字就可以看出来,就是获取指定目录下的所有内容.之前我们要遍历目录获取目录及目录下的所有文件一般是需要进行递归遍历的,自己写这个代码说 ...

  10. spring入门3-jdbcTemplate简单使用和声明式事务

    1.JdbcTemplate简单使用 1.1.引入相关依赖包 <dependency> <groupId>mysql</groupId> <artifactI ...