創作內容

2 GP

C++ - 謹慎實作virtual destructor避免記憶體洩漏

作者:巧克力喬斯達│2023-09-05 19:24:18│巴幣:4│人氣:80
先說在C++偵測記憶體洩漏的常見方法:
#include <iostream>
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

int32_t main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    int32_t* TestArray = new int32_t[100];
    memset(TestArray, 1, sizeof(int32_t) * 100);

    std::cout << "Leak testing!\n";
    return EXIT_SUCCESS;
}

很簡單一段程式,主要呼叫_CrtSetDbgFlag()這個函式來啟動偵測,最後會在輸出視窗
(不是console輸出,而是在IDE的輸出)看到以下訊息:
Detected memory leaks!
Dumping objects ->
{166} normal block at 0x000001E6BC143B60, 400 bytes long.
Data: <                > 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
Object dump complete.
The program '[345488] MemoryLeakTest.exe' has exited with code 0 (0x0).

恰好是前面故意沒釋放的陣列資料,它會告知開發者資料長度以及大概的內容
當然,只要好好使用delete[],就不會有這個洩漏

===========================================================
接著正式進入今天的例子 - 遺失virtual destructor

先看第一段 - 正常無洩漏
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

char* TestString = new char[20];
strcpy_s(TestString, 20, "I'm test string \0");
std::cout << TestString << std::endl;

delete[]TestString;

再看第二段 - string放在class裡面,依舊無洩漏
class TestClass
{
public:
    TestClass(const char* InString)
    {
        TestString = new char[20];
        memset(TestString, 0, 20);
        strcpy_s(TestString, 20, InString);
    }

    ~TestClass()
    {
        delete[]TestString;
    }

    void PrintTestString()
    {
        std::cout << TestString << std::endl;
    }

private:
    char* TestString;
};

int32_t main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    TestClass* A = new TestClass("I'm test string");
    A->PrintTestString();
    delete A;

    return EXIT_SUCCESS;
}

不過到了第三段,卻洩了:
class BaseTestClass
{
public:
    BaseTestClass() {}
    ~BaseTestClass()
    {
        std::cout << "~BaseTestClass called.\n";
    }
    virtual void PrintTestString() = 0;
};

class TestClass : public BaseTestClass
{
public:
    TestClass(const char* InString)
    {
        TestString = new char[20];
        memset(TestString, 0, 20);
        strcpy_s(TestString, 20, InString);
    }

    ~TestClass()
    {
        delete[]TestString;
        std::cout << "~TestClass called.\n";
    }

    virtual void PrintTestString() override
    {
        std::cout << TestString << std::endl;
    }

private:
    char* TestString;
};

int32_t main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    BaseTestClass* A = new TestClass("I'm test string");
    A->PrintTestString();
    delete A;

    return EXIT_SUCCESS;
}

得到的記憶體洩漏為:
Detected memory leaks!
Dumping objects ->
{167} normal block at 0x0000021A55F12E90, 20 bytes long.
Data: <I'm test string > 49 27 6D 20 74 65 73 74 20 73 74 72 69 6E 67 00
Object dump complete.

恰好是初始化的那個資料,不過怎突然出現了洩漏?
很明顯,是在繼承BaseTestClass之後所發生的
此時如果為了安全起見把char*替換成std::string也不能解決問題,會得到:
Detected memory leaks!
Dumping objects ->
{168} normal block at 0x0000019E48C13060, 16 bytes long.
Data: <8  H            > 38 DF C0 48 9E 01 00 00 00 00 00 00 00 00 00 00
Object dump complete.

雖然資料部分已經看不到完整個 "I'm test string"了,但長度16 bytes(含\0)是正確的

真正的起因就在於第三段紅色那行,在運用多型的時候出了問題,難道在delete A那一行執行的時候根本沒有呼叫到~TestClass()? 對,就是這樣,此時的輸出會是:
I'm test string
~BaseTestClass called.

這就是遺失virtual關鍵字的下場了,此時的~BaseTestClass()與~TestClass()是沒有關聯的
所以要解決這個問題
1. 老實存成TestClass指標,在delete時會先呼叫~TestClass()再叫~BaseTestClass(),但這樣就不能多型了
2. 寫成 virtual ~BaseTestClass(),如此一來也是先呼叫~TestClass()再叫~BaseTestClass()


大概就分享到這~僅僅一個keyword就造成了天地之差,設計base class時不可不慎!
如果我要帶領一個team,base class的解構子必加virtual這一點我一定會納入coding standard
引用網址:https://home.gamer.com.tw/TrackBack.php?sn=5788483
All rights reserved. 版權所有,保留一切權利

相關創作

留言共 0 篇留言

我要留言提醒:您尚未登入,請先登入再留言

2喜歡★ltes50414 可決定是否刪除您的留言,請勿發表違反站規文字。

前一篇:Unheard Engi... 後一篇:Graphics - P...

追蹤私訊切換新版閱覽

作品資料夾

overozone《小剪男孩》
如果哪天成為真正的大人、真正的人類,或許就能解開這些難題──凪玲長篇BL同人更新第30章,歡迎來看看!看更多我要大聲說昨天20:55


face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】