Pergunta

Alguém conhece uma "técnica" para descobrir vazamentos de memória causados ​​por ponteiros inteligentes?Atualmente estou trabalhando em um grande projeto escrito em C++ que usa muito ponteiros inteligentes com contagem de referência.Obviamente temos alguns vazamentos de memória causados ​​por ponteiros inteligentes, que ainda são referenciados em algum lugar do código, para que sua memória não seja liberada.É muito difícil encontrar a linha de código com a referência "desnecessária", que faz com que o objeto correspondente não seja liberado (embora não seja mais útil).

Encontrei alguns conselhos na web que propunham coletar pilhas de chamadas das operações de incremento/decremento do contador de referência.Isso me dá uma boa dica de qual trecho de código fez com que o contador de referência aumentasse ou diminuísse.

Mas o que eu preciso é de algum tipo de algoritmo que agrupe as pilhas de chamadas de "aumento/diminuição" correspondentes.Depois de remover esses pares de pilhas de chamadas, espero ter (pelo menos) um "aumento de pilha de chamadas" sobrando, que me mostra o trecho de código com a referência "desnecessária", que fez com que o objeto correspondente não fosse liberado.Agora não será grande coisa consertar o vazamento!

Mas alguém tem uma ideia de um "algoritmo" que faça o agrupamento?

O desenvolvimento ocorre sob janelas XP.

(Espero que alguém tenha entendido o que tentei explicar...)

Editar:Estou falando de vazamentos causados ​​por referências circulares.

Foi útil?

Solução

Observe que uma fonte de vazamentos com ponteiros inteligentes de contagem de referência são ponteiros com dependências circulares.Por exemplo, A tem um ponteiro inteligente para B e B tem um ponteiro inteligente para A.Nem A nem B serão destruídos.Você terá que encontrar e quebrar as dependências.

Se possível, use ponteiros inteligentes boost e use shared_ptr para ponteiros que deveriam ser proprietários dos dados, e fraco_ptr para ponteiros que não deveriam chamar delete.

Outras dicas

A maneira como faço isso é simplesmente:- Em cada pilha de chamadas de registro addref (), - a release correspondente () a remove.Assim no final do programa fico com AddRefs() sem usinar Releases.Não há necessidade de combinar pares,

Se você puder reproduzir o vazamento de forma determinística, uma técnica simples que utilizo com frequência é numerar todos os seus ponteiros inteligentes em sua ordem de construção (use um contador estático no construtor) e relatar esse ID junto com o vazamento.Em seguida, execute o programa novamente e acione um DebugBreak() quando o ponteiro inteligente com o mesmo ID for construído.

Você também deve considerar esta ótima ferramenta: http://www.codeproject.com/KB/applications/visualleakdetector.aspx

Para detectar ciclos de referência, você precisa ter um gráfico de todos os objetos contados por referência.Esse gráfico não é fácil de construir, mas pode ser feito.

Crie um global set<CRefCounted*> para registrar objetos vivos contados por referência.Isso é mais fácil se você tiver uma implementação AddRef() comum - basta adicionar this ponteiro para o conjunto quando a contagem de referência do objeto vai de 0 a 1.Da mesma forma, em Release() remova o objeto do conjunto quando sua contagem de referência for de 1 a 0.

A seguir, forneça alguma maneira de obter o conjunto de objetos referenciados de cada CRefCounted*.Poderia ser um virtual set<CRefCounted*> CRefCounted::get_children() ou o que mais lhe convier.Agora você tem uma maneira de percorrer o gráfico.

Finalmente, implemente seu algoritmo favorito para detecção de ciclo em um gráfico direcionado.Inicie o programa, crie alguns ciclos e execute o detector de ciclos.Aproveitar!:)

O que faço é agrupar o ponteiro inteligente com uma classe que leva FUNÇÃO e LINHA parâmetros.Aumente uma contagem para essa função e linha toda vez que o construtor for chamado e diminua a contagem toda vez que o destruidor for chamado.em seguida, escreva uma função que despeje as informações de função/linha/contagem.Isso informa onde todas as suas referências foram criadas

O que fiz para resolver isso foi substituir o malloc/novo & liberar/excluir operadores de forma que eles acompanhem em uma estrutura de dados o máximo possível sobre a operação que você está executando.

Por exemplo, ao substituir malloc/novo, Você pode criar um registro do endereço do chamador, a quantidade de bytes solicitados, o valor do ponteiro atribuído retornado e um ID de sequência para que todos os seus registros possam ser sequenciados (não sei se você lida com threads, mas precisa levar isso em consideração conta também).

Ao escrever o liberar/excluir rotinas, também acompanho o endereço do chamador e as informações do ponteiro.Então olho para trás na lista e tento combinar o malloc/novo contraparte usando o ponteiro como minha chave.Se eu não encontrar, levante uma bandeira vermelha.

Se você puder pagar, poderá incorporar aos seus dados o ID da sequência para ter certeza absoluta de quem e quando a chamada de alocação foi feita.A chave aqui é identificar exclusivamente cada par de transações, tanto quanto pudermos.

Então você terá uma terceira rotina exibindo seu histórico de alocações/desalocações de memória, junto com as funções que invocam cada transação.(isso pode ser feito analisando o mapa simbólico do seu vinculador).Você saberá quanta memória terá alocada a qualquer momento e quem o fez.

Se você não tiver recursos suficientes para realizar essas transações (meu caso típico para microcontroladores de 8 bits), poderá enviar as mesmas informações por meio de um link serial ou TCP para outra máquina com recursos suficientes.

Não é uma questão de encontrar um vazamento.No caso de ponteiros inteligentes, provavelmente será direcionado para algum local genérico como CreateObject(), que está sendo chamado milhares de vezes.É uma questão de determinar qual lugar no código não chamou Release() no objeto contado por referência.

Como você disse que está usando o Windows, talvez você possa aproveitar as vantagens do utilitário dump heap do modo de usuário da Microsoft, UMDH, que vem com o Ferramentas de depuração para Windows.UMDH faz instantâneos do uso de memória do seu aplicativo, registrando a pilha usada para cada alocação, e permite comparar vários instantâneos para ver quais chamadas para o alocador "vazaram" de memória.Ele também traduz os rastreamentos de pilha em símbolos para você usando dbghelp.dll.

Há também outra ferramenta da Microsoft chamada “LeakDiag” que suporta mais alocadores de memória do que UMDH, mas é um pouco mais difícil de encontrar e não parece ser mantida ativamente.A versão mais recente tem pelo menos cinco anos, se bem me lembro.

Se eu fosse você pegaria o log e escreveria um script rápido para fazer algo como o seguinte (o meu é em Ruby):

def allocation?(line)
  # determine if this line is a log line indicating allocation/deallocation
end

def unique_stack(line)
  # return a string that is equal for pairs of allocation/deallocation
end

allocations = []
file = File.new "the-log.log"
file.each_line { |line|
  # custom function to determine if line is an alloc/dealloc
  if allocation? line
    # custom function to get unique stack trace where the return value
    # is the same for a alloc and dealloc
    allocations[allocations.length] = unique_stack line
  end
}

allocations.sort!

# go through and remove pairs of allocations that equal,
# ideally 1 will be remaining....
index = 0

while index < allocations.size - 1
  if allocations[index] == allocations[index + 1]
    allocations.delete_at index
  else
    index = index + 1
  end
end

allocations.each { |line|
  puts line
}

Isso basicamente passa pelo log e captura cada alocação/desalocação e armazena um valor único para cada par, depois classifica e remove os pares correspondentes, para ver o que resta.

Atualizar:Desculpe por todas as edições intermediárias (postei acidentalmente antes de terminar)

Eu sou um grande fã de Heapchecker do Google - não detectará todos os vazamentos, mas detectará a maioria deles.(Dica:Vincule-o a todos os seus unittests.)

O primeiro passo poderia ser saber qual classe está vazando.Depois de saber disso, você poderá descobrir quem está aumentando a referência:1.coloque um ponto de interrupção no construtor da classe que é encapsulado por shared_ptr.2.intervenha com o depurador dentro de shared_ptr quando estiver aumentando a contagem de referência:Veja a variável pn-> pi _-> use_count_ pegue o endereço dessa variável avaliando a expressão (algo assim:& this-> pn-> pi_.use_count_), você receberá um endereço 3.No depurador do visual studio, vá para Debug->New Breakpoint->New Data Breakpoint...Digite o endereço da variável 4.Execute o programa.Seu programa irá parar sempre que algum ponto do código estiver aumentando e diminuindo o contador de referência.Então você precisa verificar se eles são correspondentes.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top