Quais são algumas dicas gerais para garantir que eu não vaze memória nos programas C ++? Como faço para descobrir quem deve liberar memória que foi alocada dinamicamente?
c++
memory
memory-management
raii
dulipishi
fonte
fonte
Respostas:
Em vez de gerenciar a memória manualmente, tente usar ponteiros inteligentes, quando aplicável.
Dê uma olhada no Boost lib , TR1 e ponteiros inteligentes .
Agora, ponteiros inteligentes agora fazem parte do padrão C ++ chamado C ++ 11 .
fonte
Endosso completamente todos os conselhos sobre RAII e indicadores inteligentes, mas também gostaria de adicionar uma dica de nível um pouco mais alto: a memória mais fácil de gerenciar é a memória que você nunca alocou. Diferente de linguagens como C # e Java, onde praticamente tudo é uma referência, em C ++ você deve colocar objetos na pilha sempre que puder. Como já vi várias pessoas (incluindo o Dr. Stroustrup), a principal razão pela qual a coleta de lixo nunca foi popular em C ++ é que o C ++ bem escrito não produz muito lixo em primeiro lugar.
Não escreva
ou mesmo
quando você pode escrever
fonte
Use RAII
Este post parece ser repetitivo, mas em C ++, o padrão mais básico a ser conhecido é o RAII .
Aprenda a usar ponteiros inteligentes, tanto do boost, TR1 quanto do auto_ptr modesto (mas geralmente eficiente o suficiente) (mas você deve conhecer suas limitações).
RAII é a base da segurança de exceção e do descarte de recursos em C ++, e nenhum outro padrão (sanduíche, etc.) fornecerá os dois (e na maioria das vezes, não fornecerá nenhum).
Veja abaixo uma comparação do código RAII e não-RAII:
Sobre a RAII
Para resumir (após o comentário do Ogre Psalm33 ), o RAII se baseia em três conceitos:
Isso significa que, no código C ++ correto, a maioria dos objetos não será construída
new
e, em vez disso, será declarada na pilha. E para aqueles construídos usandonew
, tudo terá um escopo de alguma forma (por exemplo, anexado a um ponteiro inteligente).Como desenvolvedor, isso é muito poderoso, pois você não precisará se preocupar com o manuseio manual de recursos (como em C ou em alguns objetos em Java que fazem uso intensivo
try
/finally
para esse caso) ...Editar (12-02-2012)
wilhelmtell está certo sobre isso: existem maneiras excepcionais de enganar a RAII, todas levando à parada abrupta do processo.
Essas são maneiras excepcionais porque o código C ++ não está repleto de terminação, saída etc., ou, no caso de exceções, queremos uma exceção não tratada para travar o processo e o core despejar sua imagem de memória como está, e não após a limpeza.
Mas ainda precisamos saber sobre esses casos, porque, embora raramente aconteçam, ainda podem acontecer.
(quem chama
terminate
ouexit
no código C ++ casual? ... Lembro-me de ter que lidar com esse problema ao jogar com o GLUT : essa biblioteca é muito orientada para C, chegando ao ponto de projetá-la ativamente para tornar as coisas difíceis para desenvolvedores de C ++, como não se importarem sobre empilhar dados alocados ou tomar decisões "interessantes" sobre nunca retornar do loop principal ... Não vou comentar sobre isso) .fonte
Você vai querer olhar para ponteiros inteligentes, como os ponteiros inteligentes do boost .
Ao invés de
O boost :: shared_ptr será excluído automaticamente quando a contagem de referência for zero:
Observe minha última observação, "quando a contagem de referência for zero, que é a parte mais legal. Portanto, se você tiver vários usuários do seu objeto, não precisará acompanhar se o objeto ainda está em uso. Depois que ninguém se refere ao seu ponteiro compartilhado, ele é destruído.
Esta não é uma panacéia, no entanto. Embora você possa acessar o ponteiro base, não desejaria passá-lo para uma API de terceiros, a menos que estivesse confiante com o que estava fazendo. Muitas vezes, as coisas de "postagem" em algum outro encadeamento para que o trabalho seja feito APÓS o término da criação. Isso é comum com PostThreadMessage no Win32:
Como sempre, use seu boné de pensamento com qualquer ferramenta ...
fonte
Leia o RAII e certifique-se de entender.
fonte
A maioria dos vazamentos de memória resulta da falta de clareza sobre a propriedade e a duração do objeto.
A primeira coisa a fazer é alocar na pilha sempre que puder. Isso lida com a maioria dos casos em que você precisa alocar um único objeto para alguma finalidade.
Se você precisar 'novo' um objeto, na maioria das vezes ele terá um único proprietário óbvio pelo resto de sua vida útil. Para esta situação, costumo usar vários modelos de coleções projetados para 'possuir' objetos armazenados neles pelo ponteiro. Eles são implementados com o vetor STL e os contêineres de mapas, mas têm algumas diferenças:
Meu problema com o STL é que ele é tão focado nos objetos Value, enquanto na maioria dos aplicativos os objetos são entidades únicas que não possuem semântica de cópia significativa necessária para uso nesses contêineres.
fonte
Bah, vocês jovens e seus novos colecionadores de lixo ...
Regras muito fortes sobre "propriedade" - qual objeto ou parte do software tem o direito de excluir o objeto. Comentários claros e nomes de variáveis sábios para tornar óbvio se um ponteiro "possui" ou é "apenas olhe, não toque". Para ajudar a decidir quem possui o quê, siga o máximo possível o padrão "sanduíche" em todas as sub-rotinas ou métodos.
Às vezes é necessário criar e destruir em lugares amplamente diferentes; Eu acho difícil evitar isso.
Em qualquer programa que exija estruturas de dados complexas, eu crio uma árvore estrita e clara de objetos contendo outros objetos - usando ponteiros "proprietários". Essa árvore modela a hierarquia básica dos conceitos de domínio de aplicativo. Exemplo: uma cena 3D possui objetos, luzes, texturas. No final da renderização, quando o programa é encerrado, existe uma maneira clara de destruir tudo.
Muitos outros ponteiros são definidos conforme necessário sempre que uma entidade precisa acessar outra, para varrer arays ou o que for; estes são os "apenas olhando". Para o exemplo da cena 3D - um objeto usa uma textura, mas não possui; outros objetos podem usar a mesma textura. A destruição de um objeto não invoca a destruição de nenhuma textura.
Sim, é demorado, mas é o que eu faço. Eu raramente tenho vazamentos de memória ou outros problemas. Mas então trabalho na arena limitada de software científico, de aquisição de dados e gráficos de alto desempenho. Não costumo negociar transações como bancos e comércio eletrônico, GUIs orientadas a eventos ou caos assíncrono em rede. Talvez os novos caminhos tenham uma vantagem por lá!
fonte
Ótima pergunta!
se você estiver usando c ++ e estiver desenvolvendo um aplicativo alto de CPU e memória em tempo real (como jogos), precisará criar seu próprio Gerenciador de Memória.
Acho que o melhor que você pode fazer é mesclar algumas obras interessantes de vários autores, posso dar uma dica:
Alocador de tamanho fixo é muito discutido, em todos os lugares da rede
A alocação de objetos pequenos foi introduzida por Alexandrescu em 2001 em seu livro perfeito "Design moderno em c ++"
Um grande avanço (com o código-fonte distribuído) pode ser encontrado em um incrível artigo na Game Programming Gem 7 (2008) chamado "High Performance Heap alocador", escrito por Dimitar Lazarov
Uma ótima lista de recursos pode ser encontrada em neste artigo
Não comece a escrever um alocador inútil inútil por si mesmo ... DOCUMENTO-SE primeiro.
fonte
Uma técnica que se tornou popular no gerenciamento de memória em C ++ é o RAII . Basicamente, você usa construtores / destruidores para lidar com a alocação de recursos. É claro que existem outros detalhes desagradáveis em C ++ devido à segurança de exceções, mas a idéia básica é bastante simples.
A questão geralmente se resume a uma questão de propriedade. Eu recomendo a leitura das séries Effective C ++ de Scott Meyers e Modern C ++ Design de Andrei Alexandrescu.
fonte
Já existe muito sobre como não vazar, mas se você precisar de uma ferramenta para ajudá-lo a rastrear vazamentos, dê uma olhada:
fonte
Use ponteiros inteligentes em todos os lugares que puder! Classes inteiras de vazamento de memória simplesmente desaparecem.
fonte
Compartilhe e conheça as regras de propriedade da memória em seu projeto. O uso das regras COM garante a melhor consistência (os parâmetros [de entrada] pertencem ao chamador, o destinatário deve copiar; os parâmetros [out] pertencem ao chamador, o destinatário deve fazer uma cópia se manter uma referência; etc.)
fonte
O valgrind é uma boa ferramenta para verificar também os vazamentos de memória dos programas em tempo de execução.
Está disponível na maioria dos tipos de Linux (incluindo Android) e no Darwin.
Se você costuma escrever testes de unidade para seus programas, deve adquirir o hábito de executar sistematicamente o valgrind nos testes. Potencialmente, evitará muitos vazamentos de memória em um estágio inicial. Também é geralmente mais fácil identificá-los em testes simples que em um software completo.
Obviamente, este conselho permanece válido para qualquer outra ferramenta de verificação de memória.
fonte
Além disso, não use memória alocada manualmente se houver uma classe de biblioteca padrão (por exemplo, vetor). Certifique-se de violar essa regra que possui um destruidor virtual.
fonte
Se você não pode / não usa um ponteiro inteligente para algo (embora isso deva ser uma grande bandeira vermelha), digite seu código com:
Isso é óbvio, mas certifique-se de digitá-lo antes de digitar qualquer código no escopo
fonte
Uma fonte frequente desses erros é quando você tem um método que aceita uma referência ou ponteiro para um objeto, mas deixa a propriedade pouco clara. Convenções de estilo e comentários podem tornar isso menos provável.
Seja o caso especial em que a função assume a propriedade do objeto. Em todas as situações em que isso acontece, certifique-se de escrever um comentário ao lado da função no arquivo de cabeçalho indicando isso. Você deve se esforçar para garantir que, na maioria dos casos, o módulo ou a classe que aloca um objeto também seja responsável por desalocá-lo.
O uso de const pode ajudar bastante em alguns casos. Se uma função não modificar um objeto e não armazenar uma referência a ele que persista após o retorno, aceite uma referência const. Ao ler o código do chamador, será óbvio que sua função não aceitou a propriedade do objeto. Você poderia ter a mesma função aceitar um ponteiro não-const e o chamador pode ou não ter assumido que o chamado aceitou a propriedade, mas com uma referência const, não há dúvida.
Não use referências não-const nas listas de argumentos. Não é muito claro ao ler o código do chamador que o receptor pode ter mantido uma referência ao parâmetro.
Não concordo com os comentários que recomendam indicadores de referência contados. Isso geralmente funciona bem, mas quando você tem um bug e não funciona, especialmente se o seu destruidor faz algo não trivial, como em um programa multithread. Definitivamente, tente ajustar seu design para não precisar de contagem de referência, se não for muito difícil.
fonte
Dicas em ordem de importância:
Dica # 1 Lembre-se sempre de declarar seus destruidores "virtuais".
Dica 2: use RAII
Dica # 3 Use os smartpointers da boost
Dica # 4 Não escreva seus próprios Smartpointers de buggy, use boost (em um projeto em que estou no momento não posso usar o boost, e sofri a necessidade de depurar meus próprios ponteiros inteligentes, eu definitivamente não aceitaria a mesma rota novamente, mas novamente agora não posso adicionar impulso às nossas dependências)
Dica # 5 Se algum trabalho ocasional / crítico para o desempenho (como em jogos com milhares de objetos) funcionar, observe o contêiner de ponteiro de impulso de Thorsten Ottosen
Dica # 6 Encontre um cabeçalho de detecção de vazamento para sua plataforma de escolha, como o cabeçalho "vld" da Visual Leak Detection
fonte
Se puder, use boost shared_ptr e C ++ auto_ptr padrão. Eles transmitem semântica de propriedade.
Quando você retorna um auto_ptr, está dizendo ao chamador que está dando a ele propriedade da memória.
Quando você retorna um shared_ptr, está dizendo ao chamador que você tem uma referência a ele e ele faz parte da propriedade, mas não é apenas responsabilidade deles.
Essas semânticas também se aplicam aos parâmetros. Se o chamador passar um auto_ptr, ele estará lhe dando propriedade.
fonte
Outros mencionaram maneiras de evitar vazamentos de memória em primeiro lugar (como ponteiros inteligentes). Mas uma ferramenta de análise de perfil e de memória geralmente é a única maneira de rastrear problemas de memória depois que você os tiver.
O memcheck do Valgrind é excelente e gratuito.
fonte
Somente para MSVC, adicione o seguinte na parte superior de cada arquivo .cpp:
Em seguida, ao depurar com o VS2003 ou superior, você será informado sobre quaisquer vazamentos quando o programa sair (ele rastreia novo / exclui). É básico, mas me ajudou no passado.
fonte
valgrind (disponível apenas para plataformas * nix) é um verificador de memória muito bom
fonte
Se você deseja gerenciar sua memória manualmente, você tem dois casos:
Se você precisar violar alguma dessas regras, documente-a.
É tudo sobre propriedade de ponteiro.
fonte
Na verdade, esse também é o mecanismo usado por "ponteiros inteligentes" e referido como RAII por alguns dos outros escritores ;-).
Dessa forma, você pode criar uma saída de depuração (quais endereços são alocados e desalocados, ...) facilmente, se necessário.
fonte
Você pode interceptar as funções de alocação de memória e verificar se há algumas zonas de memória não liberadas na saída do programa (embora não seja adequado para todos os aplicativos).
Isso também pode ser feito em tempo de compilação, substituindo os operadores new e delete e outras funções de alocação de memória.
Por exemplo, verifique neste site [Depurando alocação de memória em C ++] Nota: Há um truque para o operador de exclusão também algo como isto:
Você pode armazenar em algumas variáveis o nome do arquivo e quando o operador de exclusão sobrecarregado saberá de onde foi chamado. Dessa forma, você pode rastrear cada exclusão e malloc do seu programa. No final da sequência de verificação de memória, você deve poder relatar qual bloco de memória alocado não foi 'excluído', identificando-o pelo nome do arquivo e pelo número da linha, que é o que você deseja.
Você também pode tentar algo como o BoundsChecker no Visual Studio, que é bastante interessante e fácil de usar.
fonte
Envolvemos todas as nossas funções de alocação com uma camada que acrescenta uma breve sequência na frente e uma bandeira sentinela no final. Assim, por exemplo, você teria uma chamada para "myalloc (pszSomeString, iSize, iAlignment); ou novo (" description ", iSize) MyObject (); que aloca internamente o tamanho especificado, mais espaço suficiente para o cabeçalho e o sentinela. , não esqueça de comentar isso para compilações sem depuração! É preciso um pouco mais de memória para fazer isso, mas os benefícios superam os custos.
Isso tem três benefícios - primeiro, permite rastrear fácil e rapidamente o código que está vazando, fazendo pesquisas rápidas por códigos alocados em determinadas 'zonas', mas não limpos quando essas zonas deveriam ter sido liberadas. Também pode ser útil detectar quando um limite foi substituído, verificando se todos os sentinelas estão intactos. Isso nos salvou várias vezes ao tentar encontrar essas falhas ou erros de matriz bem ocultos. O terceiro benefício é rastrear o uso da memória para ver quem são os grandes jogadores - um agrupamento de determinadas descrições em um MemDump informa quando o 'som' ocupa muito mais espaço do que o esperado, por exemplo.
fonte
C ++ é projetado RAII em mente. Não há realmente nenhuma maneira melhor de gerenciar memória em C ++, eu acho. Mas tome cuidado para não alocar pedaços muito grandes (como objetos de buffer) no escopo local. Isso pode causar estouros de pilha e, se houver uma falha nos limites de verificação ao usar esse pedaço, você poderá sobrescrever outras variáveis ou endereços de retorno, o que leva a todos os tipos de falhas de segurança.
fonte
Um dos únicos exemplos de alocação e destruição em locais diferentes é a criação de encadeamentos (o parâmetro que você passa). Mas mesmo neste caso é fácil. Aqui está a função / método que cria um thread:
Aqui, ao invés, a função thread
Muito fácil, não é? Caso a criação do encadeamento falhe, o recurso será liberado (excluído) pelo auto_ptr, caso contrário, a propriedade será passada para o encadeamento. E se o encadeamento for tão rápido que, após a criação, libere o recurso antes do
é chamado na principal função / método? Nada! Porque 'informaremos' o auto_ptr para ignorar a desalocação. O gerenciamento de memória C ++ é fácil, não é? Felicidades,
Ema!
fonte
Gerencie a memória da mesma maneira que gerencia outros recursos (identificadores, arquivos, conexões db, soquetes ...). A GC também não ajudaria você.
fonte
Exatamente um retorno de qualquer função. Dessa forma, você pode fazer a desalocação lá e nunca perder.
Caso contrário, é muito fácil cometer um erro:
fonte