一、哪里遇到了这个问题

在进行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. TL431

    1. 设置分流电压VKA 由于分流电压调节器是非理想的: 存在动态电阻ZKA,会导致VREF存在一个偏置电压: ( IKA - INOM ) × ZKA 其中,INOM 是 VKA = VREF 测试 ...

  2. 组合数取模的几种方法--Exlucas&杨辉三角&组合

    组合数取模的几个方法 求: \[C^{m}_{n} \bmod P \] 1.杨辉三角法 \[C^{m}_{n} = C^{m - 1}_{n - 1} + C^{ m }_{n - 1} \] 时间 ...

  3. TwinCAT3 - 实现自己的Dictionary

    目录 1,前言 2,C#的字典 3,TwinCAT3的字典 定义功能块 添加方法 4,用起来 1,前言 C#有字典,TwinCAT没字典,咋办,自己写一个咯 2,C#的字典 C#的字典使用很简单,下面 ...

  4. express请求数据的获取(get和post)body-parser

    get请求 直接用res.query就可以拿到数据 post请求 需要使用中间件body-parser 第一步:安装body-parser npm i body-parser 第二步:按照模板进行使用 ...

  5. Java取模和取余,你真的弄懂了吗?

    前言 Java 中常见的取模和取余(求余)计算,在我们日常的很多业务领域都有用到.比如当我们做数据加密时,密码学中不同的加密方案底层会采用不同的模运算来决定其复杂度:做游戏的同学游戏引擎中的取余求最高 ...

  6. ES7学习笔记(二)ES的集群原理

    发现 发现是节点之间彼此发现,形成集群的一个过程.这个过程发生的场景有很多,比如:你启动了一个集群节点,或者一个节点确认主节点已经挂掉了,或者一个新的主节点被选举了. 咱们在配置集群的时候在配置文件中 ...

  7. Pipeline流水线通过git拉取Jenkinsfile报错 error: RPC failed; result=22, HTTP code = 404

    Pipeline流水线通过git拉取Jenkinsfile报错 error: RPC failed; result=22, HTTP code = 404 在学习共享库时使用通过git拉取jenkin ...

  8. 1. Two Sum Go实现

    在数组中找到 2 个数之和等于给定值的数字,结果返回 2 个数字在数组中的下标. 1. 解法1 时间复杂度 O(n^2) 直接两次遍历所有节点,进行求和比较 代码如下: func twoSum(num ...

  9. ASP.NET Core – Razor Class Library (RCL)

    前言 Razor Class Library 的用途是封装 Razor views, pages, controllers, page models, Razor components, View c ...

  10. Python条件语句 if

    语法: 示例: if elif else: