Chaque programmeur C ++ devrait être capable de trouver des fuites de mémoire. Le C ++ est un langage complexe, il est facile de faire des erreurs et les trouver peut être une corvée. Cela est particulièrement vrai pour les fuites de mémoire. La situation de détection des fuites de mémoire ne fait qu'empirer si la bibliothèque Qt est utilisée dans le code C ++.
Cet article est consacré à divers outils qui peuvent être utilisés avec plus ou moins de succès pour détecter les fuites de mémoire dans les applications C ++ / Qt (bureau). Les outils seront examinés conjointement avec l'EDI Visual Studio 2019. Cet article ne couvrira pas tous les outils possibles, mais uniquement les plus populaires et les plus efficaces.
Notre équipe étudie ces outils depuis longtemps et de près et les utilise dans leur travail. La quantité de code sur laquelle il est possible de tester de tels outils est d'environ 1,5 million de lignes. Sur la base d'une vaste expérience pratique, nous vous parlerons des avantages et des inconvénients des différents outils, vous dirons ce qu'ils sont capables de trouver et ce qui est trop difficile, parlerons de nuances non évidentes et, surtout, établirons un tableau comparatif récapitulatif basé sur un vrai exemple. Nous essaierons de vous mettre à jour aussi rapidement et simplement que possible (montrer un démarrage rapide), donc même si vous, le lecteur, n'avez jamais recherché des fuites de mémoire, cet article vous aidera à comprendre et à trouver votre première fuite dans quelques heures. Aller!
Quel est le problème?
Une fuite de mémoire est une situation où la mémoire a été allouée (par exemple, par le nouvel opérateur) et n'a pas été supprimée par erreur par l'opérateur / la fonction de suppression correspondante (par exemple, supprimer).
Exemple 1.
int* array = nullptr;
for (int i = 0; i < 5; i++)
{
array = new int[10];
}
delete[] array;
Il y a une fuite ici lors de l'allocation de mémoire pour les 4 premiers tableaux. 160 octets sont perdus. La dernière matrice est supprimée correctement. Donc, la fuite est strictement sur une seule ligne:
array = new int[10];
Exemple 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;
}
Il y a déjà plus de fuites ici: la mémoire pour a (400 octets), pour b (1200 octets) et pour test (16 octets pour x64) n'est pas supprimée. Cependant, la suppression de a et b est fournie dans le code, mais cela ne se produit pas en raison de l'absence d'appel au destructeur de test. Ainsi, il y a trois fuites, mais l'erreur qui conduit à ces fuites n'en est qu'une, et elle est générée par la ligne
Test* test = new Test;
En même temps, il n'y a aucune erreur dans le code de la classe Test.
Exemple 3.
Prenons une classe Qt, quelque chose comme ceci:
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);
}
Disons également une allocation de mémoire quelque part dans le code:
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).
Intel Inspector’ , - «Intel Inspector».
- Intel Inspector . «Detect Leaks» , (. 2). - , , , , .
«Start», . , ( , «» ), . , , . , . , (. . 1). , Intel Inspector (. 3):
, , , 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, . , .
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) .
, Visual Studio .
VLD VLD, , , .. .
VLD ( vld-2.5.1-setup.exe) , ( Path Visual Studio). .
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
.
:
#pragma once //#define LEAKS_DETECTION #ifdef LEAKS_DETECTION #include <vld.h> #endif
, , .
(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 , , , .
(. . 5, ). ViewMode -> Stacks View ( Types View), :
, 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»).
, , 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 |
- |
Trouve-t-il de vraies fuites dans le débogage |
Oui tout. Redondance des résultats - 18 fois. |
Oui tout. Redondance des résultats - 26 fois. |
Trouve-t-il de vraies fuites dans la version avec des informations de débogage |
Oui tout. Redondance des résultats - 27 fois. |
- |
Déboguer les faux positifs |
Oui un peu |
Pas |
Faux positifs dans la version avec informations de débogage |
Oui un peu |
- |
Puis-je utiliser dans l'intégration continue |
Pas |
Oui |
Tableau 7. Comparaison d'Intel Inspector et VLD.
Il est conseillé de donner la place n ° 1 dans la notation au VLD, car il ne produit pas de faux positifs, est plus stable en fonctionnement et convient mieux à une utilisation dans des scénarios d'intégration continue.