为何在C++二维指针vector中释放apriltag image_u8内存时出错?
AprilTag图像缓存释放重复内存问题分析与修复
问题场景
在多源高帧率图像流中检测AprilTag时,因回调内检测耗时过长导致丢帧,故采用缓存数秒图像后批量检测的方案。每次检测完成后需彻底释放内存(单批次5秒数据可达数GB),但第二次运行释放环节出现重复释放内存的段错误。
使用的AprilTag库图像类型定义:
typedef struct image_u8 image_u8_t; struct image_u8 { const int32_t width; const int32_t height; const int32_t stride; uint8_t *buf; };
对应的创建与销毁函数:
image_u8_t *image_u8_create_stride(unsigned int width, unsigned int height, unsigned int stride) { uint8_t *buf = calloc(height*stride, sizeof(uint8_t)); // const initializer image_u8_t tmp = { .width = width, .height = height, .stride = stride, .buf = buf }; image_u8_t *im = calloc(1, sizeof(image_u8_t)); memcpy(im, &tmp, sizeof(image_u8_t)); return im; } void image_u8_destroy(image_u8_t *im) { if (!im) return; free(im->buf); free(im); }
复现问题的最小代码
#include <iostream> #include <apriltag/apriltag.h> #include <vector> using namespace std; vector<vector<image_u8_t *>> images(2); void create_images(int i){ image_u8_t * img; img = image_u8_create(1920, 1200); images.at(i).push_back(img); } int main() { char c; for (int i = 0; i < 3; i++){ for (int j = 0; j < 2; j++){ create_images(j); } } // 第一次释放正常 for (auto vec : images){ for (auto img : vec){ image_u8_destroy(img); } vec.clear(); } cin >> c; for (int i = 0; i < 3; i++){ for (int j = 0; j < 2; j++){ create_images(j); } } // 第二次释放出现段错误 for (auto vec : images){ for (auto img : vec){ image_u8_destroy(img); } vec.clear(); } }
报错现象
第一次释放正常,第二次释放时出现重复释放同一指针的段错误:
Freeing image buffer at **0x7ff8cf22e010** Freeing image buffer at 0x7ff8cedc8010 Freeing image buffer at 0x7ff8ce962010 Freeing image buffer at 0x7ff8ceffb010 Freeing image buffer at 0x7ff8ceb95010 Freeing image buffer at 0x7ff8ce72f010 c Freeing image buffer at **0x7ff8cf22e010** Segmentation fault (core dumped)
补充输出显示,第一次clear()后原vector的旧指针并未被移除,第二次push_back后容器size变为6(旧3个+新3个),导致重复释放旧指针。
问题根源
遍历images时使用了值拷贝:for (auto vec : images),这里的vec是原vector的副本,调用vec.clear()仅清空了副本,原vector中的指针元素依然存在。第二次push_back后,原vector中同时保留了旧指针和新指针,释放时会重复释放已被销毁的旧指针,触发内存错误。
修复方案
将遍历方式改为引用遍历,直接操作原vector:
// 修改第一次释放的循环 for (auto& vec : images){ for (auto img : vec){ image_u8_destroy(img); } vec.clear(); } // 修改第二次释放的循环 for (auto& vec : images){ for (auto img : vec){ image_u8_destroy(img); } vec.clear(); }
关键修改点
- 将
auto vec改为auto& vec,让循环变量直接引用原容器中的子vector,而非创建副本。 - 此时
vec.clear()会直接清空原vector的元素,彻底移除已销毁的指针,避免后续重复释放。
额外优化建议
- 批量释放后可调用
vec.shrink_to_fit(),释放vector预留的内存空间,避免多GB数据残留的内存占用。 - 考虑使用智能指针(如
std::unique_ptr<image_u8_t>)结合自定义删除器,自动管理内存,避免手动释放出错:
// 定义自定义删除器 auto image_deleter = [](image_u8_t* img) { image_u8_destroy(img); }; // 使用unique_ptr管理图像 using ImagePtr = std::unique_ptr<image_u8_t, decltype(image_deleter)>; vector<vector<ImagePtr>> images(2); // 创建图像时 ImagePtr img(image_u8_create(1920, 1200), image_deleter); images.at(i).push_back(std::move(img)); // 释放时无需手动调用destroy,vector.clear()会自动触发删除器
内容的提问来源于stack exchange,提问作者Douglas B
相关产品推荐
相关产品推荐

