一、哪里遇到了这个问题

在进行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. 2023 ICPC 杭州题解

    游记 gym F. Top Cluster std 二分答案.需要判断点权 \(\le mid\) 的点到询问点的最大距离.直径. K. Card Game 设 \(f[l,r]\) 为 \([l,r ...

  2. 如何将Linux的NIC 名称更改为 eth0 而不是 enps33 或 enp0s25,只要几秒钟

    概述 我们使用Linux系统,网卡名称通常都是eth0,但是有一些新的linux发行版,网卡名字 enps33 或 enp0s25. peng@ubuntu:~$ ifconfig ens33 Lin ...

  3. .NET 6 使用Nlog 记录日志到本地并写入SQLserver数据库

    1. 安装Nlog 对应Nuget包版本 NLog:5.0.4 NLog.Database:5.0.4 NLog.Web.AspNetCore:5.1.4 Microsoft.Data.SqlClie ...

  4. 在 Mac 上使用 X11

    有时我们需要在服务器上运行一个 GUI 程序,然而我们是通过 SSH 连接到服务器的,看不到图形界面,怎么办呢?我们可以通过 X11 将 GUI 程序的界面转发到本地. 在 Mac 上使用 X11 需 ...

  5. 【Appium】之自动化定位总结

    一.同级定位时,先定位上级 我想定位[必填]框,我先定位[姓名]的同一个上级 self.driver.find_element(MobileBy.XPATH,"//*[contains(@t ...

  6. Mysql table 调整table的字符集和校对规则

    ALTER TABLE `xxxx`.`xxx` CHARACTER SET = utf8mb4 , COLLATE = utf8mb4_0900_ai_ci ;

  7. Go实现常用的排序算法

    一.插入排序 1.从第一个元素开始,该元素可以认为已经被排序 2.取出下一个元素,在已经排序的元素序列中从后向前扫描 3.如果该元素(已排序)大于新元素,将该元素移到下一位置 4.重复步骤3,直到找到 ...

  8. 如何使用 Redis 实现后台房间的数据管理?

    ​  ​摘要:利用 Redis 实现房间业务管理的实践与思考. 文|即构业务后台开发团队 在一些互动场景中,比如语音聊天室.电商直播等,成员控制.连麦.献花.发弹幕等互动功能,通常要求后台服务器能够储 ...

  9. OData – API Versioning

    前言 先看这 3 篇 ASP.NET Core – Web API Versioning ASP.NET Core – Swagger OpenAPI (Swashbuckle) ASP.NET Co ...

  10. ASP.NET Core – Web API Versioning

    前言 项目持续维护, API 就需要版本控制. ASP.NET Core 有官方的插件专门处理 API 版本控制. 主要参考 Your Guide to REST API Versioning in ...