先說在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