Encontrando vazamentos de memória em aplicativos C ++ / Qt

Todo programador C ++ deve ser capaz de encontrar vazamentos de memória. C ++ é uma linguagem complexa, cometer erros é fácil e encontrá-los pode ser uma tarefa árdua. Isso é especialmente verdadeiro para vazamentos de memória. A situação de captura de vazamentos de memória só piora se a biblioteca Qt for usada no código C ++.





Este artigo é dedicado a várias ferramentas que podem ser usadas com vários graus de sucesso para detectar vazamentos de memória em aplicativos C ++ / Qt (desktop). As ferramentas serão revisadas em conjunto com o IDE do Visual Studio 2019. Este artigo não cobrirá todas as ferramentas possíveis, mas apenas as mais populares e eficazes.





Nossa equipe vem estudando essas ferramentas há muito tempo e de perto e as utiliza em seu trabalho. A quantidade de código em que é possível testar essas ferramentas é de cerca de 1,5 milhão de linhas. Com base na vasta experiência prática, falaremos sobre os prós e os contras de diferentes ferramentas, o que elas são capazes de encontrar e o que é muito difícil, falaremos sobre nuances não óbvias e, o mais importante, faremos uma tabela comparativa resumida com base um exemplo real. Tentaremos atualizá-lo da forma mais rápida e simples possível (mostrar um início rápido), portanto, mesmo que você, leitor, nunca tenha procurado por vazamentos de memória, este artigo o ajudará a descobrir e encontrar seu primeiro vazamento em um par de horas. Vai!





Qual é o problema?

Um vazamento de memória é uma situação em que a memória foi alocada (por exemplo, pelo novo operador) e não foi excluída por engano pelo operador / função de exclusão correspondente (por exemplo, excluir).





Exemplo 1.





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



Há um vazamento aqui ao alocar memória para os primeiros 4 arrays. 160 bytes vazaram. A última matriz é removida corretamente. Portanto, o vazamento está estritamente em uma linha:





array = new int[10];
      
      







Exemplo 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;
}
      
      



Já existem mais vazamentos aqui: a memória para a (400 bytes), para b (1200 bytes) e para teste (16 bytes para x64) não foi excluída. No entanto, a remoção de aeb é fornecida no código, mas não ocorre devido à ausência de uma chamada para o destruidor de teste. Assim, existem três vazamentos, mas o erro que leva a esses vazamentos é apenas um, e é gerado pela linha





Test* test = new Test;
      
      



Ao mesmo tempo, não há erros no código da classe Test.









Exemplo 3.





Vamos ter uma aula de Qt, mais ou menos assim:





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);
}
      
      



Vamos também ter a alocação de memória em algum lugar do código:





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).





Figura:  1. Introdução ao Intel Inspector
. 1. Intel Inspector`

Intel Inspector’ , - «Intel Inspector».





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





Figura:  2. Guia Intel Inspector para configurá-lo e iniciá-lo
. 2. Intel Inspector`

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





Figura:  3. Um exemplo dos resultados da análise de software para vazamentos de memória usando o 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, . , .





Figura:  4. Vazamentos nas dlls do sistema.
. 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 , , , .





Figura:  5. Trabalhar com instantâneos.
. 5. .

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





Figura:  6. Trabalho com instantâneos, pilha de chamadas.
. 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»).





Figura:  8. Filtrar a lista de erros encontrados pelo utilitário PVS-Studio.
. 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





-





Ele encontra vazamentos reais na depuração





Sim tudo. Redundância de resultados - 18 vezes.





Sim tudo. Redundância de resultados - 26 vezes.





Ele encontra vazamentos reais no lançamento com informações de depuração





Sim tudo. Redundância de resultados - 27 vezes.





-





Depurar falsos positivos





sim, um pouco





Não





Falsos positivos na versão com informações de depuração





sim, um pouco





-





Posso usar em integração contínua





Não





sim





Tabela7. Comparação do Intel Inspector e VLD.





É aconselhável atribuir o lugar nº 1 na classificação ao VLD, uma vez que não produz falsos positivos, é mais estável em operação e é mais adequado para uso em cenários de integração contínua.








All Articles