關於C++當中的指標懸空問題

笑眯眯办大事發表於2024-09-03

一、哪裡遇到了這個問題

在進行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” 被賦值的是未知的,導致了 指標懸空 的問題。

相關文章