You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何在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的元素,彻底移除已销毁的指针,避免后续重复释放。

额外优化建议

  1. 批量释放后可调用vec.shrink_to_fit(),释放vector预留的内存空间,避免多GB数据残留的内存占用。
  2. 考虑使用智能指针(如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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:45:26