C++未修改代码每次运行cout输出结果异常问题排查
问题描述
在C++代码中遍历4张GeoTIFF影像,需要提取影像的geotiepoints(地理控制点坐标),存储为std::pair结构:first元素为影像编号,second元素为存储地理控制点的double数组。所有pair最终存入全局std::vector<std::pair<short, double*>>类型变量供后续调用,初始实现代码如下:
//Global Variable std::vector <std::pair<short, double*>> GeoTPWithNr; //for each file in directory void GetGeoTiePoints(TIFF* tif) { unsigned short count; double* Geotiepoints; std::pair<short, double*>TiepointsWithNumber; TiepointsWithNumber.first = filenumber; TIFFGetField(tif, TIFFTAG_GEOTIEPOINTS, &count, &Geotiepoints); // 原代码此处遗漏分号 TiepointsWithNumber.second = Geotiepoints; GeoTPWithNr.push_back(TiepointsWithNumber); std::cout << GeoTPWithNr[0].second[3] << std::endl; std::cout << GeoTPWithNr[0].second[4] << std::endl; }
测试时出现如下现象:
- 测试逻辑为打印vector中下标为0位置存储的控制点值,预期四次调用该函数时都能输出一致的控制点数值
- 首次运行代码时结果符合预期,但后续完全不修改代码重复运行,每次都会得到不同的异常输出
- 调试确认:将pair对象push到vector之前打印pair内存储的控制点值,始终能得到正确结果,初步怀疑问题出在vector写入或读取环节
问题根因
问题和vector本身的写入/读取逻辑无关,根因是存储了悬空指针,触发未定义行为:
TIFFGetField获取到的Geotiepoints指针指向libtiff库内部管理的内存,这块内存的生命周期绑定在当前传入的TIFF*句柄上:当你关闭当前TIFF句柄、后续调用libtiff接口操作该句柄、或者libtiff内部做内存复用回收时,这块内存会被释放或覆写。- 你存入vector的只是指针地址,没有拷贝指针指向的实际数据。刚调用完
TIFFGetField立刻打印时,这块内存还没被改动,所以输出正确;一旦后续操作触发libtiff回收/覆写该内存,指针就变成野指针,读取到的是内存中随机写入的垃圾值,因此每次运行结果都不一致,属于典型的内存生命周期管理错误。
修复方案
不要直接存储libtiff返回的内部指针,应当自己申请可控生命周期的内存,将控制点数据完整拷贝后再存储,优先使用标准容器自动管理内存,从根源避免野指针和内存泄漏问题。
推荐修复代码如下:
#include <vector> #include <tiffio.h> // 全局变量:second改用std::vector<double>存储控制点数据,自动管理内存 std::vector<std::pair<short, std::vector<double>>> GeoTPWithNr; // 遍历目录下每个影像文件时调用 void GetGeoTiePoints(TIFF* tif, short filenumber) { unsigned short count = 0; double* raw_tiepoints = nullptr; // 读取TIFF控制点标签,增加返回值校验 if (TIFFGetField(tif, TIFFTAG_GEOTIEPOINTS, &count, &raw_tiepoints) != 1) { // 此处可添加无控制点的错误处理逻辑 return; } std::pair<short, std::vector<double>> entry; entry.first = filenumber; // 核心步骤:将libtiff内部管理的控制点数据拷贝到自有内存中 entry.second.assign(raw_tiepoints, raw_tiepoints + count); GeoTPWithNr.push_back(std::move(entry)); // 测试打印,此时无论后续对TIFF句柄做任何操作,存储的控制点值都不会异常 std::cout << GeoTPWithNr[0].second[3] << std::endl; std::cout << GeoTPWithNr[0].second[4] << std::endl; }
如果因历史代码约束必须使用double*裸指针存储,则需要手动申请内存拷贝数据,在数据使用完成后手动释放内存:
- 存储时调用
double* my_tiepoints = new double[count];申请内存,通过memcpy将Geotiepoints指向的数据拷贝到my_tiepoints指向的自有内存,再存入vector - 所有控制点使用完成后,遍历全局vector,对每个元素的
second指针执行delete[]释放内存,避免泄漏。
内容的提问来源于stack exchange,提问作者DGIS
相关产品推荐
相关产品推荐

