1) Dopo alcune ricerche e basato su una soluzione molto bella (per Windows) di Chris Becke, ho realizzato una soluzione molto simile per Linux OS.
2) I miei obiettivi di perdita di memoria di rilevamento:
sono abbastanza chiare - rilevare le perdite, mentre pure:
2.1) Idealmente in modo preciso - indicano esattamente quanti byte sono stati assegnati ma non deallocate.
2.2) Massimo sforzo - se non esattamente, indicare in modo "falso positivo" (comunicarci una perdita anche se non è necessariamente una e allo stesso tempo NON PERDERE alcun rilevamento di perdite). È meglio essere più severi con noi stessi qui.
2.3) Poiché sto scrivendo i miei test unitari nel framework GTest, testare ogni test unitario GTest come "entità atomica".
2.4) Prendere in considerazione anche le allocazioni "C-style" (deallocations) utilizzando malloc/free.
2.5) Idealmente, prendere in considerazione il C++ "allocazioni sul posto".
2.6) Facile da utilizzare e integrare in un codice esistente (classi basate su GTest per il test dell'unità).
2.7) Avere la possibilità di "configurare" le impostazioni dei controlli principali (abilitare/disabilitare il controllo della memoria, ecc.) Per ogni test e/o tutta la classe di test.
3) Architettura Soluzione:
mia soluzione utilizza le funzionalità ereditati utilizzando il framework GTEST, quindi definisce una classe “base” per ogni classe di unit test l'aggiungeremo in futuro. Fondamentalmente, le funzionalità principali della classe base possono essere suddivise in:
3.1) Eseguire il "primo" test di stile GTest per comprendere la quantità di "memoria extra" allocata nello heap in caso di errore di test . Come Chris Becke menzionato nell'ultima frase della sua risposta sopra.
3.2) Facile da integrare - eredita semplicemente da questa classe di base e scrive le tue funzioni di "TEST_F style".
3.3.1) Per ciascun test, è possibile decidere se indicare altrimenti eseguire il controllo di perdita di memoria o meno. Questo viene eseguito tramite il set di memoria SetIgnoreMemoryLeakCheckForThisTest(). Nota: non c'è bisogno di "resettarlo" di nuovo - avverrà automaticamente per il prossimo test a causa del modo in cui i test delle unità GTest funzionano (chiamano il Ctor prior per ogni chiamata di funzione).
3.3.2) Inoltre, se per qualche motivo si conosce in anticipo che il test "mancherà" alcune allocazioni di memoria e si conosce l'importo - è possibile usufruire delle due funzioni per portare questo fatto in considerazioni una volta eseguita la verifica della memoria (che, a proposito, eseguita "semplicemente" sottraendo la quantità di memoria in uso all'inizio del test dalla quantità di memoria in uso alla fine del test).
seguito è la classe di base intestazione:
// memoryLeakDetector.h:
#include "gtest/gtest.h"
extern int g_numOfExtraBytesAllocatedByGtestUponTestFailure;
// The fixture for testing class Foo.
class MemoryLeakDetectorBase : public ::testing::Test
{
// methods:
// -------
public:
void SetIgnoreMemoryLeakCheckForThisTest() { m_ignoreMemoryLeakCheckForThisTest= true; }
void SetIsFirstCheckRun() { m_isFirstTestRun = true; }
protected:
// You can do set-up work for each test here.
MemoryLeakDetectorBase();
// You can do clean-up work that doesn't throw exceptions here.
virtual ~MemoryLeakDetectorBase();
// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:
// Code here will be called immediately after the constructor (right
// before each test).
virtual void SetUp();
// Code here will be called immediately after each test (right
// before the destructor).
virtual void TearDown();
private:
void getSmartDiff(int naiveDiff);
// Add the extra memory check logic according to our
// settings for each test (this method is invoked right
// after the Dtor).
virtual void PerformMemoryCheckLogic();
// members:
// -------
private:
bool m_ignoreMemoryLeakCheckForThisTest;
bool m_isFirstTestRun;
bool m_getSmartDiff;
size_t m_numOfBytesNotToConsiderAsMemoryLeakForThisTest;
int m_firstCheck;
int m_secondCheck;
};
E qui è la fonte di questa classe di base:
// memoryLeakDetectorBase.cpp
#include <iostream>
#include <malloc.h>
#include "memoryLeakDetectorBase.h"
int g_numOfExtraBytesAllocatedByGtestUponTestFailure = 0;
static int display_mallinfo_and_return_uordblks()
{
struct mallinfo mi;
mi = mallinfo();
std::cout << "========================================" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << "Total non-mmapped bytes (arena):" << mi.arena << std::endl;
std::cout << "# of free chunks (ordblks):" << mi.ordblks << std::endl;
std::cout << "# of free fastbin blocks (smblks):" << mi.smblks << std::endl;
std::cout << "# of mapped regions (hblks):" << mi.hblks << std::endl;
std::cout << "Bytes in mapped regions (hblkhd):"<< mi.hblkhd << std::endl;
std::cout << "Max. total allocated space (usmblks):"<< mi.usmblks << std::endl;
std::cout << "Free bytes held in fastbins (fsmblks):"<< mi.fsmblks << std::endl;
std::cout << "Total allocated space (uordblks):"<< mi.uordblks << std::endl;
std::cout << "Total free space (fordblks):"<< mi.fordblks << std::endl;
std::cout << "Topmost releasable block (keepcost):" << mi.keepcost << std::endl;
std::cout << "========================================" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << std::endl;
std::cout << std::endl;
return mi.uordblks;
}
MemoryLeakDetectorBase::MemoryLeakDetectorBase()
: m_ignoreMemoryLeakCheckForThisTest(false)
, m_isFirstTestRun(false)
, m_getSmartDiff(false)
, m_numOfBytesNotToConsiderAsMemoryLeakForThisTest(0)
{
std::cout << "MemoryLeakDetectorBase::MemoryLeakDetectorBase" << std::endl;
m_firstCheck = display_mallinfo_and_return_uordblks();
}
MemoryLeakDetectorBase::~MemoryLeakDetectorBase()
{
std::cout << "MemoryLeakDetectorBase::~MemoryLeakDetectorBase" << std::endl;
m_secondCheck = display_mallinfo_and_return_uordblks();
PerformMemoryCheckLogic();
}
void MemoryLeakDetectorBase::PerformMemoryCheckLogic()
{
if (m_isFirstTestRun) {
std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - after the first test" << std::endl;
int diff = m_secondCheck - m_firstCheck;
if (diff > 0) {
std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - setting g_numOfExtraBytesAllocatedByGtestUponTestFailure to:" << diff << std::endl;
g_numOfExtraBytesAllocatedByGtestUponTestFailure = diff;
}
return;
}
if (m_ignoreMemoryLeakCheckForThisTest) {
return;
}
std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic" << std::endl;
int naiveDiff = m_secondCheck - m_firstCheck;
// in case you wish for "more accurate" difference calculation call this method
if (m_getSmartDiff) {
getSmartDiff(naiveDiff);
}
EXPECT_EQ(m_firstCheck,m_secondCheck);
std::cout << "MemoryLeakDetectorBase::PerformMemoryCheckLogic - the difference is:" << naiveDiff << std::endl;
}
void MemoryLeakDetectorBase::getSmartDiff(int naiveDiff)
{
// according to some invastigations and assumemptions, it seems like once there is at least one
// allocation which is not handled - GTest allocates 32 bytes on the heap, so in case the difference
// prior for any further substrcutions is less than 32 - we will assume that the test does not need to
// go over memory leak check...
std::cout << "MemoryLeakDetectorBase::getMoreAccurateAmountOfBytesToSubstructFromSecondMemoryCheck - start" << std::endl;
if (naiveDiff <= 32) {
std::cout << "MemoryLeakDetectorBase::getSmartDiff - the naive diff <= 32 - ignoring..." << std::endl;
return;
}
size_t numOfBytesToReduceFromTheSecondMemoryCheck = m_numOfBytesNotToConsiderAsMemoryLeakForThisTest + g_numOfExtraBytesAllocatedByGtestUponTestFailure;
m_secondCheck -= numOfBytesToReduceFromTheSecondMemoryCheck;
std::cout << "MemoryLeakDetectorBase::getSmartDiff - substructing " << numOfBytesToReduceFromTheSecondMemoryCheck << std::endl;
}
void MemoryLeakDetectorBase::SetUp()
{
std::cout << "MemoryLeakDetectorBase::SetUp" << std::endl;
}
void MemoryLeakDetectorBase::TearDown()
{
std::cout << "MemoryLeakDetectorBase::TearDown" << std::endl;
}
// The actual test of this module:
TEST_F(MemoryLeakDetectorBase, getNumOfExtraBytesGTestAllocatesUponTestFailureTest)
{
std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - START" << std::endl;
// Allocate some bytes on the heap and DO NOT delete them so we can find out the amount
// of extra bytes GTest framework allocates upon a failure of a test.
// This way, upon our legit test failure, we will be able to determine of many bytes were NOT
// deleted EXACTLY by our test.
std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - size of char:" << sizeof(char) << std::endl;
char* pChar = new char('g');
SetIsFirstCheckRun();
std::cout << "MemoryLeakDetectorPocTest::getNumOfExtraBytesGTestAllocatesUponTestFailureTest - END" << std::endl;
}
Infine, un campione "basata GTEST" classe unit test che utilizza questo basa le classi e illustra gli usi e diversi POC (proof of concept) diversi per ogni tipo di allocazioni e verifiche diverse se siamo in grado (o meno) di rilevare le mancate allocazioni.
// memoryLeakDetectorPocTest.cpp
#include "memoryLeakDetectorPocTest.h"
#include <cstdlib> // for malloc
class MyObject
{
public:
MyObject(int a, int b) : m_a(a), m_b(b) { std::cout << "MyObject::MyObject" << std::endl; }
~MyObject() { std::cout << "MyObject::~MyObject" << std::endl; }
private:
int m_a;
int m_b;
};
MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest()
{
std::cout << "MemoryLeakDetectorPocTest::MemoryLeakDetectorPocTest" << std::endl;
}
MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest()
{
std::cout << "MemoryLeakDetectorPocTest::~MemoryLeakDetectorPocTest" << std::endl;
}
void MemoryLeakDetectorPocTest::SetUp()
{
std::cout << "MemoryLeakDetectorPocTest::SetUp" << std::endl;
}
void MemoryLeakDetectorPocTest::TearDown()
{
std::cout << "MemoryLeakDetectorPocTest::TearDown" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - START" << std::endl;
// allocate some bytes on the heap and intentially DONT release them...
const size_t numOfCharsOnHeap = 23;
std::cout << "size of char is:" << sizeof(char) << " bytes" << std::endl;
std::cout << "allocating " << sizeof(char) * numOfCharsOnHeap << " bytes on the heap using new []" << std::endl;
char* arr = new char[numOfCharsOnHeap];
// DO NOT delete it on purpose...
//delete [] arr;
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeType - END" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - START" << std::endl;
std::cout << "size of MyObject is:" << sizeof(MyObject) << " bytes" << std::endl;
std::cout << "allocating MyObject on the heap using new" << std::endl;
MyObject* myObj1 = new MyObject(12, 17);
delete myObj1;
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedType - END" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyMallocAllocationForNativeType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - START" << std::endl;
size_t numOfDoublesOnTheHeap = 3;
std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - sizeof double is " << sizeof(double) << std::endl;
std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - allocaitng " << sizeof(double) * numOfDoublesOnTheHeap << " bytes on the heap" << std::endl;
double* arr = static_cast<double*>(malloc(sizeof(double) * numOfDoublesOnTheHeap));
// NOT free-ing them on purpose !!
// free(arr);
std::cout << "MemoryLeakDetectorPocTest::verifyMallocAllocationForNativeType - END" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForNativeSTLVectorType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - START" << std::endl;
std::vector<int> vecInt;
vecInt.push_back(12);
vecInt.push_back(15);
vecInt.push_back(17);
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForNativeSTLVectorType - END" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyNewAllocationForUserDefinedSTLVectorType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - START" << std::endl;
std::vector<MyObject*> vecMyObj;
vecMyObj.push_back(new MyObject(7,8));
vecMyObj.push_back(new MyObject(9,10));
size_t vecSize = vecMyObj.size();
for (int i = 0; i < vecSize; ++i) {
delete vecMyObj[i];
}
std::cout << "MemoryLeakDetectorPocTest::verifyNewAllocationForUserDefinedSTLVectorType - END" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationAndDeAllocationForUserDefinedType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - START" << std::endl;
void* p1 = malloc(sizeof(MyObject));
MyObject *p2 = new (p1) MyObject(12,13);
p2->~MyObject();
std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationAndDeAllocationForUserDefinedType - END" << std::endl;
}
TEST_F(MemoryLeakDetectorPocTest, verifyInPlaceAllocationForUserDefinedType)
{
std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - START" << std::endl;
void* p1 = malloc(sizeof(MyObject));
MyObject *p2 = new (p1) MyObject(12,13);
// Dont delete the object on purpose !!
//p2->~MyObject();
std::cout << "MemoryLeakDetectorPocTest::verifyInPlaceAllocationForUserDefinedType - END" << std::endl;
}
Il file di intestazione di questa classe:
// memoryLeakDetectorPocTest.h
#include "gtest/gtest.h"
#include "memoryLeakDetectorBase.h"
// The fixture for testing class Foo.
class MemoryLeakDetectorPocTest : public MemoryLeakDetectorBase
{
protected:
// You can do set-up work for each test here.
MemoryLeakDetectorPocTest();
// You can do clean-up work that doesn't throw exceptions here.
virtual ~MemoryLeakDetectorPocTest();
// Code here will be called immediately after the constructor (right
// before each test).
virtual void SetUp();
// Code here will be called immediately after each test (right
// before the destructor).
virtual void TearDown();
};
spero che sia utile e vi prego di farmi sapere se c'è qualcosa che non è chiaro.
Cheers,
Guy.
Questa è una soluzione molto "pulita". L'ho provato (in un caso certamente semplice) e ha funzionato come previsto. +1 – sevaxx
Lo metterò in secondo piano. Questa è una soluzione davvero pulita.La semplice chiamata della funzione _CrtDumpMemoryLeaks non funziona con google test perché segnala erroneamente alcune perdite nel framework, ma questa soluzione evita il suddetto problema. Devi però ricordarti di creare un'istanza della classe all'inizio di ogni test case. – tathagata
Ho appena aggiunto questa soluzione a un ampio insieme di centinaia di test. È possibile aggiungere un puntatore come variabile di classe e allocarlo in TEST_METHOD_INITIALIZE ed eliminarlo in TEST_METHOD_CLEANUP. In questo modo è solo una volta per TEST_CLASS – SecsAndCyber