Ou é mais como "descartar objetos em C ++ é realmente complicado - passo 20% do meu tempo nele e, no entanto, vazamentos de memória são um lugar comum"?
Na minha experiência pessoal em C ++ e até C, vazamentos de memória nunca foram um grande esforço a evitar. Com o procedimento de teste sensato e o Valgrind, por exemplo, qualquer vazamento físico causado por uma chamada operator new/malloc
sem correspondência delete/free
é frequentemente detectado e corrigido rapidamente. Para ser justo, algumas grandes bases de códigos C ++ ou C ++ da velha escola podem ter alguns casos de borda obscuros que podem vazar fisicamente alguns bytes de memória aqui e ali, como resultado de não deleting/freeing
naquele caso de borda que voou sob o radar dos testes.
No entanto, no que diz respeito às observações práticas, os aplicativos mais vazios que encontro (como os que consomem mais e mais memória quanto mais tempo você os executa, mesmo que a quantidade de dados com os quais trabalhamos não esteja aumentando) normalmente não são escritos em C ou C ++. Não encontro coisas como o Linux Kernel ou o Unreal Engine ou mesmo o código nativo usado para implementar o Java na lista de softwares com vazamentos que encontro.
O tipo mais proeminente de software com vazamento que costumo encontrar são coisas como miniaplicativos em Flash, como jogos em Flash, mesmo que eles usem coleta de lixo. E isso não é uma comparação justa se alguém deduzir algo disso, já que muitos aplicativos Flash são escritos por desenvolvedores iniciantes que provavelmente carecem de bons princípios de engenharia e procedimentos de teste (e da mesma forma, tenho certeza de que existem profissionais qualificados trabalhando com a GC que não lute contra software com vazamento), mas eu teria muito a dizer para quem pensa que o GC impede a gravação de software com vazamento.
Ponteiros pendurados
Agora, vindo do meu domínio específico, experiência e como um usando principalmente C e C ++ (e espero que os benefícios do GC variem dependendo de nossas experiências e necessidades), a coisa mais imediata que o GC resolve para mim não são problemas práticos de vazamento de memória, mas oscilando o acesso do ponteiro, e isso poderia literalmente ser um salva-vidas em cenários de missão crítica.
Infelizmente, em muitos casos em que o GC resolve o que seria um acesso de ponteiro danificado, ele substitui o mesmo tipo de erro do programador por um vazamento de memória lógica.
Se você imaginar esse jogo em Flash escrito por um codificador iniciante, ele pode armazenar referências a elementos do jogo em várias estruturas de dados, fazendo com que compartilhem a propriedade desses recursos do jogo. Infelizmente, digamos que ele cometa um erro ao esquecer de remover os elementos do jogo de uma das estruturas de dados ao avançar para o próximo estágio, impedindo que sejam liberados até que o jogo inteiro seja encerrado. No entanto, o jogo ainda parece funcionar bem porque os elementos não estão sendo desenhados ou afetam a interação do usuário. No entanto, o jogo está começando a usar cada vez mais memória, enquanto as taxas de quadros funcionam em uma apresentação de slides, enquanto o processamento oculto ainda circula por essa coleção oculta de elementos no jogo (que agora se tornou explosivo em tamanho). Esse é o tipo de problema que encontro com frequência nesses jogos em Flash.
- Eu encontrei pessoas dizendo que isso não conta como um "vazamento de memória" porque a memória ainda está sendo liberada após o fechamento do aplicativo e, em vez disso, pode ser chamada de 'vazamento de espaço' ou algo nesse sentido. Embora essa distinção possa ser útil para identificar e falar sobre problemas, não considero essas distinções tão úteis nesse contexto se estivermos falando sobre ela como se não fosse tão problemática quanto um "vazamento de memória" quando estivermos lidando o objetivo prático de garantir que o software não consuma quantidades ridículas de memória quanto mais tempo o executamos (a menos que falemos de sistemas operacionais obscuros que não liberam a memória de um processo quando ele é finalizado).
Agora vamos dizer que o mesmo desenvolvedor iniciante escreveu o jogo em C ++. Nesse caso, normalmente haveria apenas uma estrutura central de dados no jogo que "possui" a memória enquanto outros apontam para essa memória. Se ele cometer o mesmo tipo de erro, é provável que, ao avançar para a próxima etapa, o jogo travar como resultado do acesso a indicadores pendentes (ou pior, fazer algo diferente de travar).
Esse é o tipo mais imediato de troca que eu costumo encontrar no meu domínio com mais frequência entre o GC e o GC. Na verdade, eu não ligo muito para o GC no meu domínio, o que não é muito crítico, porque as maiores lutas que já tive com software com vazamentos envolveram o uso aleatório do GC em uma equipe anterior, causando o tipo de vazamento descrito acima .
No meu domínio particular, na verdade, prefiro o software travando ou danificando-o em muitos casos, porque isso é pelo menos muito mais fácil de detectar do que tentar descobrir por que o software está misteriosamente consumindo quantidades explosivas de memória depois de executá-lo por meia hora enquanto todo o nosso os testes de unidade e integração passam sem nenhuma reclamação (nem mesmo da Valgrind, pois a memória está sendo liberada pelo GC após o desligamento). No entanto, isso não é um slam da GC da minha parte, nem uma tentativa de dizer que é inútil ou algo assim, mas não houve nenhum tipo de bala de prata, nem mesmo de perto, nas equipes com as quais trabalhei contra software com vazamento (para pelo contrário, tive a experiência oposta de que uma base de código utilizando o GC é a mais vazada que já encontrei). Para ser justo, muitos membros dessa equipe nem sabiam o que eram referências fracas,
Propriedade compartilhada e psicologia
O problema que encontro com a coleta de lixo que pode torná-lo tão propenso a "vazamentos de memória" (e insistirei em chamá-lo como tal, como o 'vazamento de espaço' se comporta exatamente da mesma maneira da perspectiva do usuário) nas mãos de aqueles que não o usam com cuidado se relacionam com "tendências humanas" até certo ponto em minha experiência. O problema com essa equipe e a base de código mais vazia que eu já encontrei foi que eles pareciam ter a impressão de que o GC permitiria que parassem de pensar em quem possui recursos.
No nosso caso, tínhamos tantos objetos referenciando um ao outro. Os modelos referenciam os materiais, juntamente com a biblioteca de materiais e o sistema de sombreamento. Os materiais referenciam as texturas junto com a biblioteca de texturas e certos shaders. As câmeras armazenariam referências a todos os tipos de entidades de cena que deveriam ser excluídas da renderização. A lista parecia continuar indefinidamente. Isso fez com que praticamente todos os recursos pesados do sistema pertencessem e estendessem sua vida útil em mais de 10 outros lugares no estado do aplicativo de uma só vez, e isso era muito, muito propenso a erros humanos de um tipo que se traduziriam em vazamentos (e não menor, estou falando de gigabytes em minutos com sérios problemas de usabilidade). Conceitualmente, todos esses recursos não precisavam ser compartilhados na propriedade, todos eles conceitualmente tinham um proprietário,
Se pararmos de pensar em quem possui qual memória e, felizmente, apenas armazenar referências que duram a vida toda a objetos em todo o lugar sem pensar nisso, o software não falhará como resultado de indicadores pendentes, mas quase certamente, sob esse mentalidade descuidada, comece a vazar memória como louca de maneiras muito difíceis de rastrear e que escapam a testes.
Se há um benefício prático para o ponteiro oscilante no meu domínio, é que ele causa falhas e travamentos muito desagradáveis. E isso tende a pelo menos incentivar os desenvolvedores, se eles querem enviar algo confiável, para começar a pensar sobre gerenciamento de recursos e fazer as coisas apropriadas necessárias para remover todas as referências / indicadores adicionais para um objeto que não é mais necessário conceitualmente.
Gerenciamento de recursos de aplicativos
Gerenciamento adequado de recursos é o nome do jogo, se estivermos falando de evitar vazamentos em aplicativos de longa duração, com estado persistente sendo armazenado, onde o vazamento pode causar sérios problemas de taxa de quadros e usabilidade. E gerenciar corretamente os recursos aqui não é menos difícil com ou sem GC. O trabalho não é menos manual para remover as referências apropriadas aos objetos que não são mais necessários, sejam eles ponteiros ou referências que prolongam a vida útil.
Esse é o desafio em meu domínio, não esquecendo o delete
que fazemos new
(a menos que falemos horas amadoras com testes, práticas e ferramentas de má qualidade). E isso requer reflexão e cuidado, se estamos usando o GC ou não.
Multithreading
A outra questão que considero muito útil com o GC, se puder ser usada com muito cuidado em meu domínio, é simplificar o gerenciamento de recursos em contextos de multithreading. Se tomarmos cuidado para não armazenar referências que estendem a vida útil a recursos em mais de um local no estado do aplicativo, a natureza de extensão da vida útil das referências de GC pode ser extremamente útil como uma maneira de os segmentos estenderem temporariamente um recurso que está sendo acessado. sua vida útil por apenas uma curta duração, conforme necessário para o encadeamento concluir o processamento.
Eu acho que o uso muito cuidadoso do GC dessa maneira pode produzir um software muito correto, que não está vazando, enquanto simultaneamente simplifica o multithreading.
Existem maneiras de contornar isso, embora o GC esteja ausente. No meu caso, unificamos a representação da entidade da cena do software, com threads que temporariamente fazem com que os recursos da cena sejam estendidos por breves períodos de forma bastante generalizada antes de uma fase de limpeza. Isso pode parecer um pouco com o GC, mas a diferença é que não há "propriedade compartilhada" envolvida, apenas um design de processamento de cena uniforme em encadeamentos que adiam a destruição dos referidos recursos. Mesmo assim, seria muito mais simples confiar apenas no GC aqui se ele pudesse ser usado com muito cuidado com desenvolvedores conscientes, com cuidado para usar referências fracas nas áreas persistentes relevantes, para esses casos de multithreading.
C ++
Finalmente:
Em C ++, tenho que chamar delete para descartar um objeto criado no final de seu ciclo de vida.
No C ++ moderno, isso geralmente não é algo que você deve fazer manualmente. Não se trata tanto de esquecer de fazê-lo. Quando você envolve o tratamento de exceções na imagem, mesmo que você tenha escrito uma delete
chamada abaixo para alguma chamada new
, algo pode aparecer no meio e nunca alcançá-la delete
se você não confiar nas chamadas destruidoras automáticas inseridas pelo compilador para fazer isso por você.
Com o C ++, você praticamente precisa, a menos que esteja trabalhando como um contexto incorporado, com exceções desativadas e bibliotecas especiais programadas deliberadamente para não serem lançadas, evite a limpeza manual de recursos (que inclui evitar chamadas manuais para desbloquear um mutex fora de um dtor , por exemplo, e não apenas desalocação de memória). O tratamento de exceções exige muito, portanto toda a limpeza de recursos deve ser automatizada através de destruidores em sua maior parte.