为什么我得到错误释放由apriltags image_u8_create()分配的内存,并存储在c++指针的2D矢量?&



我正在尝试检测图像流中的apriltags。由于图像以高速率来自多个源,因此在图像回调中检测标记将花费太长时间,导致由于错过回调而丢弃图像。我决定一次存储图像几秒钟,然后对图像运行检测。在每次运行图像之间,我希望释放所有已使用的内存,因为我需要为每~5秒运行存储多个GB的数据,并且图像/帧率/源在运行之间会发生变化。

我使用的是从源代码编译的apriltag库附带的image_u8_t类型:

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;
};

,具有create()destroy()函数(create()是填充所示create_from_stride()的一些默认值的包装器,即stride = width:

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);
}

映像的第一次运行总是按照预期进行,然而,在第二次运行时,我总是在释放内存时遇到错误。似乎在使用clear()之后,虽然向量报告size=0,但它在向量的前面保留值并迭代它们,试图将可用内存翻倍。下面是同样显示错误的最小代码示例:

#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);
}
}
// This works fine
for (auto vec : images){
for (auto img : vec){
image_u8_destroy(img);
}
vec.clear();
}
// Just to pause and inspect output
cin >> c;
for (int i = 0; i < 3; i++){
for (int j = 0; j < 2; j++){
create_images(j);
}
}
// This causes a segfault/free() error 
for (auto vec : images){
for (auto img : vec){
image_u8_destroy(img);
}
vec.clear();
}
}

打印要释放的指针(im->buf)显示了发生的事情:

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)

和我实际程序的输出显示了一个更具体但类似的问题:

img u8 vector length: 92
Destroyed all images and cleared vector. New size = 0
Destroyed all images and cleared vector. New size = 0
Destroyed all images and cleared vector. New size = 0
Destroyed all images and cleared vector. New size = 0
Freeing image buffer at 0x7f2834000b80
free(): invalid pointer

如果我误解了矢量的工作方式,特别是clear()函数,或者指向我可能导致这个问题的地方,有人能解释吗?

编辑以添加输出,即使在清除并使size()返回0之后,在下一个push_back()s上,旧值似乎重新出现在向量中:

Vector size before 1st clear: 3
Freeing image buffer at 0x7f5e34fe6010
Freeing image buffer at 0x7f5e34b80010
Freeing image buffer at 0x7f5e3471a010
Vector size after 1st clear: 0
Vector size before 1st clear: 3
Freeing image buffer at 0x7f5e34db3010
Freeing image buffer at 0x7f5e3494d010
Freeing image buffer at 0x7f5e344e7010
Vector size after 1st clear: 0
c
Vector size before 2nd clear: 6
Freeing image buffer at 0x7f5e34fe6010
Segmentation fault (core dumped)
// This works fine
for (auto vec : images){
for (auto img : vec){
image_u8_destroy(img);
}
vec.clear();
}

看起来它工作得很好,但for (auto vec : images)中的auto vec是一个值,而不是一个引用。它在images中复制vector,这意味着vec.clear();清除了一个副本。原来的images仍然包含image_u8实例,这些实例保存着现在悬空的指针。

如果我注意到

Vector size before 2nd clear: 6

诊断,我应该在一个小时前第一次被问到这个问题的时候就明白了。调试好。Asker看的是正确的东西,只是错过了一个让很多人惊讶的细节。在c++中,除非你请求引用,或者传递数组,否则你会得到一个值。

解决方案:

// This REALLY works fine
for (auto &vec : images){
for (auto &img : vec){ // optional. You probably won't save much since `img` is 
// already a pointer
image_u8_destroy(img);
}
vec.clear();
}

最新更新