重新赋值std::unique_ptr<QAudioBuffer>成员引发堆损坏问题
问题现象
构建std::unique_ptr<QAudioBuffer>类型的类成员时触发运行时异常:
- 首次在按钮点击槽函数中为该智能指针赋值时程序运行正常
- 当
AudioConfig参数变更导致sizeInBytes取值变化、需要重新赋值时,程序触发崩溃,Visual Studio弹出HEAP_CORRUPTION_DETECTED(检测到堆损坏)提示 - 崩溃点定位在
const auto data = std::make_unique<char[]>(sizeInBytes)对应的数组智能指针析构阶段,堆栈显示堆内存损坏错误
槽函数代码如下:
void MainWindow::on_start_clicked() { // 第二次进入该函数时在createAudioBuffer内部触发堆损坏 mpAudioBuffer = createAudioBuffer(*mpAudioConfig); ... }
createAudioBuffer函数原实现:
std::unique_ptr<QAudioBuffer> MainWindow::createAudioBuffer(const AudioConfig& rConfig) const { const auto bytesPerSample = (rConfig.sampleFormat == SampleFormat::Uint8) ? 1 : (rConfig.sampleFormat == SampleFormat::Int32) ? 4 : (rConfig.sampleFormat == SampleFormat::Int16) ? 2 : -1; const auto sizeInBytes = bytesPerSample * rConfig.channelCount * rConfig.frameCount; // 为传入QByteArray构造函数分配原始数据内存 const auto data = std::make_unique<char[]>(sizeInBytes); // TODO 初始化data内容 return std::make_unique<QAudioBuffer>(QByteArray( data.get(), sizeInBytes), [&rConfig]()-> QAudioFormat { // 用lambda直接构造QAudioFormat,避免单独定义变量 QAudioFormat format; format.setSampleRate(static_cast<int>(rConfig.sampleRate)); format.setChannelCount(static_cast<int>(rConfig.channelCount)); format.setSampleFormat(QAudioFormat::Int32); return format; }()); }
崩溃堆栈:
ucrtbased.dll!free_dbg_nolock(void * const block, const int block_use) Line 952 C++ ucrtbased.dll!_free_dbg(void * block, int block_use) Line 1030 C++ afdx.exe!operator delete(void * block) Line 38 C++ afdx.exe!operator delete[](void * block) Line 32 C++ afdx.exe!std::default_delete<char [0]>::operator()<char,0>(char * _Ptr) Line 3156 C++ afdx.exe!std::unique_ptr<char [0],std::default_delete<char [0]>>::~unique_ptr<char [0],std::default_delete<char [0]>>() Line 3390 C++ afdx.exe!MainWindow::createAudioBuffer(const AudioConfig & rConfig) Line 544 C++
根本原因
崩溃的核心原因是内存分配大小与QAudioBuffer实际访问的内存大小不匹配,导致越界写破坏堆元数据:
- 代码中计算
bytesPerSample时根据rConfig.sampleFormat动态返回1/2/4,但构造QAudioFormat时硬编码为QAudioFormat::Int32(对应4字节每样本),两者逻辑不一致。 - 首次运行时配置的样本格式刚好是Int32,分配的内存大小和QAudioBuffer按格式访问的内存范围一致,不会触发越界;当配置切换为Uint8/Int16时,计算得到的
sizeInBytes变小,但QAudioBuffer仍按4字节每样本的规则读写内存,越界写入破坏了堆块的控制信息。 - 当函数内局部智能指针
data析构释放内存时,堆管理器检测到元数据被篡改,直接抛出堆损坏错误。 - 额外隐患:
bytesPerSample的兜底值为-1,一旦传入不支持的样本格式,负数转换为无符号类型的size_t时会得到一个极大的正整数,导致内存分配大小完全不符合预期,进一步加剧内存访问错误。
修复方案
- 统一格式逻辑:根据配置的样本格式同步计算单样本字节数、设置QAudioFormat的样本类型,保证内存分配大小和QAudioBuffer的访问范围完全匹配。
- 增加参数校验:移除不合理的-1兜底分支,遇到不支持的样本格式直接抛出异常或返回错误,避免非法值导致的内存大小计算错误。
- 简化冗余逻辑:去掉立即执行的lambda构造QAudioFormat的写法,直接在函数内构造format对象,降低代码复杂度;构造QByteArray时明确将数据指针转为
const char*,确保调用深拷贝版本的构造函数,避免意外触发内存接管逻辑。
修复后的代码实现:
std::unique_ptr<QAudioBuffer> MainWindow::createAudioBuffer(const AudioConfig& rConfig) const { int bytesPerSample = 0; QAudioFormat::SampleFormat qtSampleFormat; // 统一映射样本格式、单样本字节数,避免不匹配 switch(rConfig.sampleFormat) { case SampleFormat::Uint8: bytesPerSample = 1; qtSampleFormat = QAudioFormat::UInt8; break; case SampleFormat::Int16: bytesPerSample = 2; qtSampleFormat = QAudioFormat::Int16; break; case SampleFormat::Int32: bytesPerSample = 4; qtSampleFormat = QAudioFormat::Int32; break; default: throw std::invalid_argument("不支持的音频样本格式"); } // 显式做类型转换,避免整数溢出、类型不匹配问题 const qsizetype sizeInBytes = static_cast<qsizetype>(bytesPerSample) * static_cast<qsizetype>(rConfig.channelCount) * static_cast<qsizetype>(rConfig.frameCount); auto data = std::make_unique<char[]>(sizeInBytes); // TODO 初始化音频数据内容 QAudioFormat format; format.setSampleRate(static_cast<int>(rConfig.sampleRate)); format.setChannelCount(static_cast<int>(rConfig.channelCount)); format.setSampleFormat(qtSampleFormat); // 显式转const char*,确保QByteArray执行深拷贝 QByteArray audioData(static_cast<const char*>(data.get()), sizeInBytes); return std::make_unique<QAudioBuffer>(audioData, format); }
内容的提问来源于stack exchange,提问作者johnco3
相关产品推荐
相关产品推荐

