Auffinden von Speicherlecks in C ++ / Qt-Anwendungen

Jeder C ++ - Programmierer sollte in der Lage sein, Speicherlecks zu finden. C ++ ist eine komplexe Sprache, Fehler zu machen ist einfach und es kann mühsam sein, sie zu finden. Dies gilt insbesondere für Speicherlecks. Die Situation beim Auffangen von Speicherlecks wird nur schlimmer, wenn die Qt-Bibliothek im C ++ - Code verwendet wird.





Dieser Artikel befasst sich mit verschiedenen Tools, die mit unterschiedlichem Erfolg verwendet werden können, um Speicherlecks in C ++ / Qt-Anwendungen (Desktop) zu erkennen. Die Tools werden in Verbindung mit der Visual Studio 2019-IDE überprüft. In diesem Artikel werden nicht alle möglichen Tools behandelt, sondern nur die beliebtesten und effektivsten.





Unser Team hat solche Werkzeuge lange und gründlich untersucht und verwendet sie in ihrer Arbeit. Die Menge an Code, mit der solche Tools getestet werden können, beträgt ungefähr 1,5 Millionen Zeilen. Basierend auf umfangreichen praktischen Erfahrungen werden wir Sie über die Vor- und Nachteile verschiedener Tools informieren, Ihnen sagen, was sie finden können und was zu schwierig ist, über nicht offensichtliche Nuancen sprechen und vor allem eine zusammenfassende Vergleichstabelle erstellen, die auf basiert ein echtes Beispiel. Wir werden versuchen, Sie so schnell und einfach wie möglich auf den neuesten Stand zu bringen (zeigen Sie einen schnellen Start). Selbst wenn Sie als Leser noch nie nach Speicherlecks gesucht haben, hilft Ihnen dieser Artikel dabei, Ihr erstes Leck herauszufinden und zu finden ein paar Stunden. Gehen!





Was ist das Problem?

Ein Speicherverlust ist eine Situation, in der Speicher zugewiesen wurde (z. B. vom neuen Operator) und nicht fälschlicherweise vom entsprechenden Löschoperator / der entsprechenden Löschfunktion (z. B. Löschen) gelöscht wurde.





Beispiel 1.





int* array = nullptr;
for (int i = 0; i < 5; i++)
{
	array = new int[10];
}
delete[] array;
      
      



Beim Zuweisen von Speicher für die ersten 4 Arrays tritt hier ein Leck auf. 160 Bytes sind durchgesickert. Das letzte Array wird korrekt entfernt. Das Leck befindet sich also ausschließlich in einer Zeile:





array = new int[10];
      
      







Beispiel 2.





class Test
{
public:
	Test()
	{
		a = new int[100];
		b = new int[300];
	}
	~Test()
	{
		delete[] a;
		delete[] b;
	}

private:
	int* a;
	int* b;
};

int main()
{
	Test* test = new Test;

	return 0;
}
      
      



Hier gibt es bereits weitere Lecks: Der Speicher für a (400 Byte), für b (1200 Byte) und für Test (16 Byte für x64) wird nicht gelöscht. Das Entfernen von a und b ist im Code vorgesehen, geschieht jedoch nicht, da kein Aufruf an den Test-Destruktor erfolgt. Somit gibt es drei Lecks, aber der Fehler, der zu diesen Lecks führt, ist nur einer und wird von der Leitung erzeugt





Test* test = new Test;
      
      



Gleichzeitig gibt es keine Fehler im Code der Testklasse.









Beispiel 3.





Lassen Sie uns eine Qt-Klasse haben, ungefähr so:





class InfoRectangle : public QLabel
{
	Q_OBJECT

public:
	InfoRectangle(QWidget* parent = nullptr);

private slots:
	void setInfoTextDelayed();

private:
	QTimer* _textSetTimer;
};
InfoRectangle::InfoRectangle(QWidget* parent)
	: QLabel(parent)
{
	_textSetTimer = new QTimer(this);
	_textSetTimer->setInterval(50);
	connect(_textSetTimer, &QTimer::timeout, this, &InfoRectangle::setInfoTextDelayed);
}

void InfoRectangle::setInfoTextDelayed()
{
	// do anything
	setVisible(true);
}
      
      



Lassen Sie uns auch irgendwo im Code Speicherzuordnung haben:





InfoRectangle* rectangle = new InfoRectangle();
      
      



, delete? , Qt. , , :





mnuLayout->addWidget(rectangle);
rectangle->setParent(this);
      
      



– . , : , . – InfoRectangle



. – QTimer,



_textSetTimer



Qt. , – connect



.





, new :
template <typename Func1, typename Func2>
    static inline QMetaObject::Connection connect(
const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
                Qt::ConnectionType type = Qt::AutoConnection)
    {
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        const int *types = nullptr;
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();

        return connectImpl(sender, reinterpret_cast<void **>(&signal),
                           receiver, reinterpret_cast<void **>(&slot),
                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<
typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                          	typename SignalType::ReturnType>(slot),
                            type, types, &SignalType::Object::staticMetaObject);
    } 

      
      



, . , . , Qt, connect, Qt, , , .





: , .   . – , , , , , ( ). , . , , .





, , , ? …





– , , , . . , . , . , () . , , .





















-













1.5





: , 1, 2, ,









7





253





1. .





, .





Intel Inspector

Intel Inspector – , Visual Studio . Intel Inspector , , , .





Intel Inspector Intel Parallel Studio 2019, Intel Inspector, . Visual Studio 2019 Intel Parallel Studio. , Intel Inspector Visual Studio (. 1).





Zahl:  1. Erste Schritte mit Intel Inspector
. 1. Intel Inspector`

Intel Inspector’ , - «Intel Inspector».





- Intel Inspector . «Detect Leaks» , (. 2). - , , , , .





Zahl:  2. Registerkarte Intel Inspector zum Konfigurieren und Starten
. 2. Intel Inspector`

«Start», . , ( , «» ), . , , . , . , (. . 1). , Intel Inspector (. 3):





Zahl:  3. Ein Beispiel für die Ergebnisse der Softwareanalyse auf Speicherlecks mit Intel Inspector.
. 3. Intel Inspector.

, , , call-stack .  , . . – IDE!





, . debug , release . ++- , debug , release (   20 ), debug' . – release (, ),   . Intel Inspector' , . , release , .





: Intel Inspector ( ) , debug release. (. 1).









,







, ,













Release c





10





70





7





Debug





101





973





9,6





2. Intel Inspector`





, . . , , , , , , 10 . ( debug), 100 . ( , ) .





– ? ? , Intel Inspector`?









- : n









: r





: (n-r)/n





: N/n





: N













Release c





7





192





168





24





0





1 (100%)





27





Debug





7





129





107





22





0





1 (100%)





18





3. Intel Inspector





, Intel Inspector . , . . , Intel Inspector, , , , , «» ( 2 3, . ).





, Intel Inspector , – . , , release , debug. , , – , .





.





1. dll.





Intel Inspector dll, . , .





Zahl:  4. Lecks in System-DLLs.
. 4. dll.

2. aligned_malloc



.





m_pVitData = (VITDEC_DATA*)_aligned_malloc(sizeof(VITDEC_DATA), 16);
m_pDcsnBuf = (byte*)_aligned_malloc(64 * (VITM6_BUF_LEN + VITM6_MAX_WND_LEN), 16);
...
_aligned_free(m_pDcsnBuf);
_aligned_free(m_pVitData);
      
      



, "" release, debug .





3. Pragma.







#pragma omp parallel for schedule(dynamic)
for (int portion = 0; portion < portionsToProcess; ++portion)
{
}
      
      



#pragma



!





, - ( Intel Inspector, VS, ..) , – . , (<50000 ) Intel Inspector . – , .





Intel Inspector – , ( ), . release , ( , ), debug. debug .





, Intel Inspector . , , . ,    «» Intel Inspector, , «» .





Visual Leak Detector

Visual Leak Detector ( VLD) – , Output (IDE Visual Studio 2019) .





  1. , Visual Studio .





  2. VLD VLD, , , .. .





  3. VLD ( vld-2.5.1-setup.exe) , ( Path Visual Studio). .





  4. VLD dll- Visual Studio 2019, dbghelp.dll C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\Extensions\TestPlatform\Extensions\Cpp\x64



    C:\Program Files (x86)\Visual Leak Detector\bin\Win64



    .





  5. :





    #pragma once
    
    //#define LEAKS_DETECTION
    
    #ifdef LEAKS_DETECTION
    #include <vld.h>
    #endif
          
          



    , , .





  6. (pp) . , solution.









#define LEAKS_DETECTION
      
      



solution. (F5) , . debug. Release c .





VLD Output. , call-stack , .





, VLD
---------- Block 652047 at 0x0000000027760070: 8787200 bytes ----------
  Leak Hash: 0x02B5C300, Count: 1, Total 8787200 bytes
  Call Stack (TID 30996):
    ucrtbased.dll!malloc()
    d:\agent\_work\63\s\src\vctools\crt\vcstartup\src\heap\new_array.cpp (29): SniperCore.dll!operator new[]()
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\ddec\S2Ldfg.cpp (445): SniperCore.dll!CS2Ldfg::CreateLLRTbls() + 0xD bytes
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\ddec\S2Ldfg.cpp (217): SniperCore.dll!CS2Ldfg::SetModeEB()
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\ddec\S2Ldfg.cpp (1447): SniperCore.dll!CS2Ldfg::Set() + 0xA bytes
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\ddec\ddec.cpp (509): SniperCore.dll!DFBase::instanceS2Dec()
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\ddec\ddec.cpp (58): SniperCore.dll!DFBase::DFBase() + 0xF bytes
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\ddec\ddec.cpp (514): SniperCore.dll!DgbS5FecAnlzr::DgbS5FecAnlzr() + 0xA bytes
    D:\SOURCE\SAP_Git\sap_win64\core\alg\fbg\fbganalyser.cpp (45): SniperCore.dll!TechnicalLayer::FBGAnalyser::FBGAnalyser() + 0x21 bytes
    D:\SOURCE\SAP_Git\sap_win64\core\engine\handlers\fbganalysishandler.cpp (218): SniperCore.dll!TechnicalLayer::FBGAnalysisHandler::init() + 0x2A bytes
    D:\SOURCE\SAP_Git\sap_win64\core\engine\handlers\fbganalysishandler.cpp (81): SniperCore.dll!TechnicalLayer::FBGAnalysisHandler::enqueueRequest()
    D:\SOURCE\SAP_Git\sap_win64\core\engine\threadedhandler2.cpp (57): SniperCore.dll!TotalCore::ThreadedHandler2::run()
    Qt5Cored.dll!QTextStream::realNumberPrecision() + 0x89E8E bytes
    kernel32.dll!BaseThreadInitThunk() + 0xD bytes
    ntdll.dll!RtlUserThreadStart() + 0x1D bytes
  Data:
    00 00 00 00    01 01 01 01    01 01 01 02    02 02 02 02     ........ ........
    02 02 03 03    03 03 03 03    03 04 04 04    04 04 04 04     ........ ........
    05 05 05 05    05 05 05 05    06 06 06 06    06 06 06 07     ........ ........
    07 07 07 07    07 07 08 08    08 08 08 08    08 09 09 09     ........ ........
    09 09 09 09    0A 0A 0A 0A    0A 0A 0A 0B    0B 0B 0B 0B     ........ ........
    0B 0B 0C 0C    0C 0C 0C 0C    0C 0D 0D 0D    0D 0D 0D 0D     ........ ........
    0E 0E 0E 0E    0E 0E 0E 0E    0F 0F 0F 0F    0F 0F 0F 10     ........ ........
    10 10 10 10    10 10 11 11    11 11 11 11    11 12 12 12     ........ ........
    EE EE EE EE    EF EF EF EF    EF EF EF F0    F0 F0 F0 F0     ........ ........
    F0 F0 F1 F1    F1 F1 F1 F1    F1 F2 F2 F2    F2 F2 F2 F2     ........ ........
    F3 F3 F3 F3    F3 F3 F3 F3    F4 F4 F4 F4    F4 F4 F4 F5     ........ ........
    F5 F5 F5 F5    F5 F5 F6 F6    F6 F6 F6 F6    F6 F7 F7 F7     ........ ........
    F7 F7 F7 F7    F8 F8 F8 F8    F8 F8 F8 F9    F9 F9 F9 F9     ........ ........
    F9 F9 FA FA    FA FA FA FA    FA FB FB FB    FB FB FB FB     ........ ........
    FC FC FC FC    FC FC FC FC    FD FD FD FD    FD FD FD FE     ........ ........
    FE FE FE FE    FE FE FF FF    FF FF FF FF    FF 00 00 00     ........ ........


---------- Block 2430410 at 0x000000002E535B70: 48 bytes ----------
  Leak Hash: 0x7062B343, Count: 1, Total 48 bytes
  Call Stack (TID 26748):
    ucrtbased.dll!malloc()
    d:\agent\_work\63\s\src\vctools\crt\vcstartup\src\heap\new_scalar.cpp (35): SniperCore.dll!operator new() + 0xA bytes
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\include\xmemory (78): SniperCore.dll!std::_Default_allocate_traits::_Allocate()
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\include\xmemory (206): SniperCore.dll!std::_Allocate<16,std::_Default_allocate_traits,0>() + 0xA bytes
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\include\xmemory (815): SniperCore.dll!std::allocator<TotalCore::TaskResult *>::allocate()
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\include\vector (744): SniperCore.dll!std::vector<TotalCore::TaskResult *,std::allocator<TotalCore::TaskResult *> >::_Emplace_reallocate<TotalCore::TaskResult * const &>() + 0xF bytes
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\include\vector (708): SniperCore.dll!std::vector<TotalCore::TaskResult *,std::allocator<TotalCore::TaskResult *> >::emplace_back<TotalCore::TaskResult * const &>() + 0x1F bytes
    C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.28.29333\include\vector (718): SniperCore.dll!std::vector<TotalCore::TaskResult *,std::allocator<TotalCore::TaskResult *> >::push_back()
    D:\SOURCE\SAP_Git\sap_win64\include\core\engine\task.h (119): SniperCore.dll!TotalCore::LongPeriodTask::setTmpResult()
    D:\SOURCE\SAP_Git\sap_win64\include\core\engine\discretestephandler.h (95): SniperCore.dll!TotalCore::DiscreteStepHandler::setResult()
    D:\SOURCE\SAP_Git\sap_win64\core\engine\handlers\prmbdtcthandler.cpp (760): SniperCore.dll!TechnicalLayer::PrmbDtctHandler::setContResult() + 0x1A bytes
    D:\SOURCE\SAP_Git\sap_win64\core\engine\handlers\prmbdtcthandler.cpp (698): SniperCore.dll!TechnicalLayer::PrmbDtctHandler::processPortion()
    D:\SOURCE\SAP_Git\sap_win64\core\engine\threadedhandler2.cpp (109): SniperCore.dll!TotalCore::ThreadedHandler2::tryProcess()
    D:\SOURCE\SAP_Git\sap_win64\core\engine\threadedhandler2.cpp (66): SniperCore.dll!TotalCore::ThreadedHandler2::run()
    Qt5Cored.dll!QTextStream::realNumberPrecision() + 0x89E8E bytes
    kernel32.dll!BaseThreadInitThunk() + 0xD bytes
    ntdll.dll!RtlUserThreadStart() + 0x1D bytes
  Data:
    10 03 51 05    00 00 00 00    B0 B4 85 09    00 00 00 00     ..Q..... ........
    60 9D B9 08    00 00 00 00    D0 1B 24 06    00 00 00 00     `....... ..$.....
    30 B5 4F 11    00 00 00 00    CD CD CD CD    CD CD CD CD     0.O..... ........

      
      



:





Visual Leak Detector detected 383 memory leaks (253257876 bytes).
Largest number used: 555564062 bytes.
Total allocations: 2432386151 bytes.
Visual Leak Detector is now exiting.
      
      



, ,





No memory leaks detected.
Visual Leak Detector is now exiting.
      
      



  debug : VLD . , release ( ) vld . . 4 release debug. (. . 1).









,





, VLD,





VLD





VLD





Debug





101





172





1,7





Release c





10





-





-





4. VLD





? ? , VLD?









- : n









: r





: (n-r)/n





: N/n





: N













Debug





7





185





185





0





0





1 (100%)





26





5. VLD





, VLD . . , VLD, , , , , «» ( 2 3, . ). - , ( ), «». , , :





connect(arrowKeyHandler, &ArrowKeyHandler::upPressed,
			[this] { selectNeighbourSignal(TopSide); }); 
      
      



, , , , connect (. 3). - .





, VLD , . continuous integration.





Visual Leak Detector – , ( ) . VLD , , , Intel Inspector debug. , «» , continuous integration.





. , vld . , , .





VS 2019

IDE Visual Studio 2019 – Diagnostic Tools. (snapshots). () .  , , , .





( debug release c ).   Diagnostic Tools. Memory Usage, Heap Profiling Take Snapshot.





, - , , . , , . , .





Break All , , , .





Zahl:  5. Arbeiten mit Schnappschüssen.
. 5. .

(. . 5, ). ViewMode -> Stacks View ( Types View), :





Zahl:  6. Arbeiten mit Snapshots, Call-Stack.
. 6. , call-stack.

, Qt: , Qt. , . (. . 1), . , .





, (, ..). , , . ( ) .





PVS-Studio

, , - PVS-Studio. , . , solution. , «», .





. PVS-Studio Visual Studio 2019, «Extensions».





solution` Extensions->PVS-Studio->Check. «PVS-Studio» , «» High, Medium Low.





, , PVS-Studio . , , : V599, V680, V689, V701, V772, V773, V1005, V1023 ( . ).





Visual Studio Tools -> Options -> PVS-Studio «Detectable Errors (C++)» , ( «Hide All», ) – . 8. «Detectable Errors (C#)» ( «Hide All» «Disabled»).





Zahl:  8. Filtern der Liste der vom Dienstprogramm PVS-Studio gefundenen Fehler.
. 8. PVS-Studio .

, , PVS-Studio High, Medium Low .





, , 1.5 2269 . Intel Core i7 4790K. (debug release) , ( , - , ).









- : n









: r





: (n-r)/n

















30





7





2





0





2





7





0 %





6. PVS-Studio





, - (Intel Inspector, VLD). , . , PVS-Studio .





, 2 – Intel Inspector Visual Leak Detector. :  









Intel Inspector





VLD





























()

























debug





9.6





1,7





release





7





-





Findet es echte Lecks im Debug





Ja alle. Redundanz der Ergebnisse - 18-mal.





Ja alle. Redundanz der Ergebnisse - 26 mal.





Findet es echte Lecks in der Version mit Debug-Informationen?





Ja alle. Redundanz der Ergebnisse - 27 Mal.





- -





Debuggen Sie falsch positive Ergebnisse





Ja, ein wenig





Nein





False Positives in Release mit Debug-Informationen





Ja, ein wenig





- -





Kann ich in Continuous Integration verwenden





Nein





Ja





Tabelle 7. Vergleich von Intel Inspector und VLD.





Es ist ratsam, VLD den ersten Platz in der Bewertung einzuräumen, da es keine falsch positiven Ergebnisse liefert, stabiler im Betrieb ist und sich besser für die Verwendung in Szenarien mit kontinuierlicher Integration eignet.








All Articles