一、哪里遇到了这个问题

在进行MNN机器学习框架的MFC应用开发的时候遇到了这个问题,在窗口控件代码段 “MNN_Inference_BarCode_MFCDlg.cpp” 当中需要进行输入图片的读取。通过opnecv2库创建cv:Mat对象,具体代码如下,是一个按钮的控件代码。重点关注其中指针操作的内容

//按钮1,用于选择图片文件
void CMNNInferenceBarCodeMFCDlg::OnBnClickedButton1()
{
CFileDialog fileDlg(TRUE, _T("*.txt"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T(" Png Files (*.png)|*.png| Jpg Files (*.jpg)|*.jpg| bmp Files (*.bmp)|*.bmp| BMP Files (*.BMP)|*.BMP| All Files (*.*)|*.*||")); if (fileDlg.DoModal() == IDOK)
{
NIU::m_configureinfo_Dlg.picture_filepath = fileDlg.GetPathName(); // 保存图片文件路径
SetDlgItemText(IDC_EDIT1, NIU::m_configureinfo_Dlg.picture_filepath); LoadImagePaths(); // 加载图片路径 // 将CString转换为std::string,用于后续加载图像
std::string imagePathStr(CT2A(NIU::m_configureinfo_Dlg.picture_filepath.GetString())); // 使用OpenCV加载图像并获取尺寸
cv::Mat image = cv::imread(imagePathStr); if (!image.empty())
{
// 保存图像的宽度和高度到类的成员变量
NIU::m_imageinfo_Dlg.img_width = image.cols; // 图像的宽度
NIU::m_imageinfo_Dlg.img_height = image.rows; // 图像的高度
// 指向图像数据,这里使用成员变量而不是局部变量是为了保证变量的生命周期,避免当OnBnClickedButton1()函数结束之后,变量被销毁导致指针成为悬空指针 // 将加载的图像存储到成员变量 m_imageMat 中
m_imageMat = image; //请仔细看这里!!!!!!!!!!!!!!
// 指向图像数据
NIU::m_imageinfo_Dlg.img_data_pt = m_imageMat.data; // 显示图像在图片控件上
DisplayImage(m_imageMat);
}
else
{
AfxMessageBox(_T("无法加载图像文件,请检查文件路径。"));
CButton* check1 = (CButton*)GetDlgItem(IDC_CHECK1);
CButton* check2 = (CButton*)GetDlgItem(IDC_CHECK2);
check1->SetCheck(BST_UNCHECKED); // 取消选中 Check1
check2->SetCheck(BST_UNCHECKED); // 取消选中 Check2
} }
}

代码当中的img_data_pt为另外一份代码 “AIEngineCommon.cpp” 当中类的一个成员,具体如下:

class NIU
{ public:
// 用于保存输入图像信息的结构体
typedef struct ImageInfo
{
Coordinate_VOC roi_coord; // [输入]ROI坐标
void* img_data_pt; // [输入]图片存储地址
int img_height; // [输入]图像高度
int img_width; // [输入]图像宽度
prep_type_t prep_type; // [输入]前处理类型
}; //声明静态变量
static ImageInfo m_imageinfo_Dlg;
}

可以看到在opencv读取完图像之后,通过 “m_imageMat.data” 这个图像指针指向图像像素数据的首地址,然后将这个指针赋值给NIU类当中的 “img_data_pt” 指针。这样方便图像存储地址可以跨文件调用。

这里需要注意的是这个指向图像像素数据的首地址指针,要么是属于全局变量,要么是数据类当中的成员变量,千万不能是这个函数当中的局部变量。这里设计到两个概念,分别是:

  • 指针有效性

    (1)指针有效性是非常重要的概念,涉及到指针是否指向了合法的内存地址。

    (2)初始化:指针在使用前应该被初始化。未初始化的指针可能指向任意内存区域,这会导致未定义行为。

    (3)分配内存:在使用指针之前,通常需要为其分配内存。例如,在C++中,可以使用 new 关键字为指针分配内存。

    (4)释放内存:当指针不再需要时,应该释放它所指向的内存。在C++中,这通常是通过 delete 关键字完成的。如果忘记释放内存,可能会导致内存泄漏。

    (5)生命周期:指针的有效性与它所指向的数据的生命周期有关。如果数据被销毁(例如,一个局部变量离开了它的作用域),那么指向它的指针就变得无效。

    (6)野指针:如果指针被释放了内存,但没有将其设置为 nullptr,那么它就变成了野指针。野指针指向的是一个不再有效的内存地址,试图访问它可能会导致程序崩溃。

    (7)悬挂指针:当指针指向的内存被释放后,如果再次被分配给另一个对象,那么原来的指针就变成了悬挂指针。如果通过悬挂指针访问数据,可能会访问到错误的数据。

    (8)指针与对象的关系:指针的有效性也与它所指向的对象的状态有关。如果对象被修改,那么指针可能需要更新以反映这种变化。

    (9)多线程环境:在多线程环境中,指针的有效性更加复杂,因为多个线程可能同时访问和修改指针和它所指向的数据。

    (10)智能指针:为了避免手动管理内存,可以使用智能指针(如C++中的 std::unique_ptr、std::shared_ptr),它们可以自动管理内存的分配和释放。

  • 变量生命周期

    (1)局部变量:

    定义在函数或代码块内部的变量称为局部变量。

    它们的生命周期从定义时开始,到函数或代码块执行结束时结束。

    局部变量在函数调用结束后会被销毁,它们通常存储在栈(stack)上。

    (2)全局变量:

    全局变量是在函数外部定义的变量,它们在整个程序的执行期间都是可见的。

    它们的生命周期从程序开始执行时开始,到程序结束时结束。

    全局变量通常存储在数据段(data segment)或BSS段(如果未初始化)。

    (3)静态变量:

    静态变量是使用 static 关键字声明的变量,它们的生命周期贯穿整个程序的执行期间。

    静态局部变量只在定义它们的函数或代码块中可见,每次函数调用时它们都会保留上一次的值。

    静态全局变量则在整个程序中可见,但它们的作用域可能被限制在定义它们的文件内。

    (4)动态分配的变量:

    使用动态内存分配(如C++中的 new 或C中的 malloc)创建的变量,它们的生命周期由程序员控制。

    必须使用相应的释放函数(如 delete 或 free)来手动管理这些变量的生命周期,否则可能会导致内存泄漏。

    (5)线程局部变量:

    在多线程环境中,线程局部变量是每个线程独有的,它们在线程的生命周期内有效。

    线程结束时,线程局部变量会被销毁。

    (6)对象的成员变量:

    对象的成员变量(也称为属性或字段)的生命周期与对象本身相同。

    当对象被创建时,成员变量被初始化;当对象被销毁时,成员变量也会随之销毁。

    (7)自动变量:

    在某些编程语言中,如C和C++,自动变量是局部变量的一种,它们在进入作用域时自动创建,在离开作用域时自动销毁。

    (8)寄存器变量:

    寄存器变量是存储在CPU寄存器中的变量,它们通常用于优化性能,因为访问寄存器比访问内存更快。

    寄存器变量的生命周期通常与它们所在的作用域相同。

    (9)常量:

    常量是一旦初始化后其值就不能被改变的变量。

    它们的生命周期可以是局部的、全局的、静态的等,这取决于它们是如何声明的。

因此,如果上述 “img_data_pt” 指针被赋值的对象是一个局部变量,比如:

// 使用OpenCV加载图像并获取尺寸
cv::Mat image = cv::imread(imagePathStr);
if (!image.empty())
{
NIU::m_imageinfo_Dlg.img_width = image.cols; // 图像的宽度
NIU::m_imageinfo_Dlg.img_height = image.rows; // 图像的高度
m_imageMat = image;
NIU::m_imageinfo_Dlg.img_data_pt = image.data;
// 显示图像在图片控件上
DisplayImage(m_imageMat);
}

那么当该函数结束的时候,CV::Mat image实例就会被销毁,导致 “img_data_pt” 被赋值的是未知的,导致了 指针悬空 的问题。

关于C++当中的指针悬空问题的更多相关文章

  1. C++中关于指针初始化和使用NULL的理解

    1.严禁使用未被初始化的指针:C++创建指针的时候,只分配存储地址的内存,并不会分配存储数据的内存,所以指针可能指向任何位置. (1)使用解除运算符(*)之前,一定要对指针初始化,否则若声明的指针刚好 ...

  2. 从汇编看c++多重继承中this指针的变化

    先来看一下下面的c++源码: #include <iostream> using namespace std; class X { public: virtual void print1( ...

  3. C编程的指针涛 ---第九笔记

    //这里说的是一个指针,指向算法的应用 //直接排序 //每个排序算法是指针指向的每个元件的特性的方便的交流 //这里的基本思想是,处理的记录的排序n - 1第二选择. //第i次操作选择i大(小)的 ...

  4. C++类指针类型的成员变量的浅复制与深复制

    本篇文章旨在阐述C++类的构造,拷贝构造,析构机制,以及指针成员变量指针悬空问题的解决.需要读者有较好的C++基础,熟悉引用,const的相关知识. 引言: 类作为C++语言的一种数据类型,是对C语言 ...

  5. C++ 智能指针Auto_PTR 分析

    C++的动态内存的分配与释放是个挺折磨人的事情,尤其异常分支复杂时(比如一堆try catch中,各catch里需要做delete 掉相关的堆上分配的内存),极有可能产生内存泄露的情况.C++中提供了 ...

  6. 2-Linux C语言指针与内存-学习笔记

    Linux C语言指针与内存 前面我们对于: c语言的基本用法 makeFile文件的使用 main函数的详解 标准输入输出流以及错误流管道 工具与原理 指针与内存都是c语言中的要点与难点 指针 数组 ...

  7. C++ 指针形参和指针引用形参的原理分析

    C++ 函数的参数传递可以分为:值传递和引用传递. 两者的最大区别也很简单,如果该函数的参数只是读的话,值传递就可以满足.如果该函数的参数需要进行修改并返回的时候,就应该进行引用传递. C++指针作为 ...

  8. C++管理指针成员

    1.C++中一般採用以下三种方法之中的一个管理指针成员: (1)指针成员採取常规行为. 这种类具有指针的全部缺陷:具有指针成员且使用默认复制构造函数和赋值操作符,无法避免悬垂指针(两个对象的指针成员指 ...

  9. C++指针和结构体基础知识

    学习C++首先要回忆起C语言当中的指针和结构体知识,本文作者将通过一段代码来总结指针和结构体基础知识:指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址.就像其他变量或常量一样,您必须在使 ...

  10. Rust所有权及引用

    Rust 所有权和借用 Rust之所以可以成为万众瞩目的语言, 就是因为其内存安全性. 在以往内存安全几乎全都是通过GC的方式实现, 但是GC会引来性能.CPU以及Stop The World等问题, ...

随机推荐

  1. C++11新特性(二):语言特性

    C++11新特性 nullptr空指针 nullptr空指针的使用可以规避掉以往设置为NULL的风险.NULL在编译器中常常被设置为0或者其它数字,此时判断指针是否为NULL,即判断指针类型是否能够等 ...

  2. Maven 配置程序入口

    配置单个程序入口 Exec Maven Plugin 插件允许你在 Maven 生命周期中的某个阶段直接运行 Java 类. 在你的 pom.xml 文件中添加如下配置: <project> ...

  3. 用描述程序的方式emo,扎心了...

    用描述程序的方式emo,扎心了... 众所周知写程序是个枯燥无聊的过程,再加上生活的不顺与坎坷,当程序语言与emo结合起来,看谁还说程序员不懂感情! 首当其冲的就是循环语句了 世界上最寂寞的感觉,是我 ...

  4. ocelot 从15.x版本升级到16.x版本 UnableToFindDownstreamRouteError Message: Failed to mat ch Route configuration for upstream path

    项目里面用到 ocelot ,之前老的项目用的是 15.x 最近要一个新项目也要用到,直接安装了最新的16.x,结果死活都匹配不到上游路径. 刚开始以为是自己代码写得有问题,各种找问题,结果后来把oc ...

  5. 【YashanDB数据库】Mybatis-plus分页框架识别不到Yashandb

    问题描述 Mybatis-plus 无法识别Yashandb数据库,应用有如下报错. 问题分析 从Mybatis-plus源码里面看到,getDBtype函数是没有Yashandb的方言. 当Yash ...

  6. 火山引擎VeDI赋能小城酒店业,助力“流量”向“留量”转化

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群.   今年,"去小城过假期"正悄然流行.根据途牛旅游发布的<2024年上半年度旅游消费报 ...

  7. Failed to convert value of type 'java.lang.String' to required type

    DEBUG 微信小程序Java后台 Failed to convert value of type 'java.lang.String' to required type 产生这种条件的原因一般是使用 ...

  8. Figma 学习笔记 – Frame

    Frame = <div> Frame 就类似 HTML 中的 div, 它和形状 rectangle 特性上蛮相识的, 但是使用场景其实差很多, 所以不要搞错哦. (除了图片很少会用到 ...

  9. IDEA如何自动导入依赖的jar包

    前言 我们在使用IDEA开发时,会引入第三方的jar包,这些第三方的jar包使我们可以快速的使用别人开发好的功能,而不用重复造轮子了. 这大大提高了我们的开发效率. 但是,有时候我们一下子需要导入太多 ...

  10. 【赵渝强老师】SQL的字符函数

    字符函数,顾名思义,操作的就是字符串.通过下图,我们来了解一下Oracle的字符函数. 一.大小写控制函数 lower.upper.initcap select lower('Hello World' ...