Java tem um GC automático que de vez em quando para o mundo, mas cuida do lixo em uma pilha. Agora, os aplicativos C / C ++ não têm esses congelamentos do STW; o uso de memória também não aumenta infinitamente. Como esse comportamento é alcançado? Como são tratados os objetos mortos?
c++
c
garbage-collection
Ju Shua
fonte
fonte
new
.What happens to garbage in C++?
Geralmente não é compilado em um executável?Respostas:
O programador é responsável por garantir que os objetos que eles criaram via
new
sejam excluídos viadelete
. Se um objeto for criado, mas não destruído antes do último ponteiro ou referência a ele ficar fora do escopo, ele entrará em conflito e se tornará um vazamento de memória .Infelizmente para C, C ++ e outras linguagens que não incluem um GC, isso simplesmente se acumula ao longo do tempo. Isso pode fazer com que um aplicativo ou o sistema fique sem memória e não consiga alocar novos blocos de memória. Nesse ponto, o usuário deve recorrer ao encerramento do aplicativo para que o sistema operacional possa recuperar a memória usada.
Quanto a mitigar esse problema, há várias coisas que facilitam a vida de um programador. Estes são suportados principalmente pela natureza do escopo .
Aqui, criamos duas variáveis. Eles existem no escopo do bloco , conforme definido pelas
{}
chaves. Quando a execução sair desse escopo, esses objetos serão excluídos automaticamente. Nesse caso,variableThatIsAPointer
como o próprio nome indica, é um ponteiro para um objeto na memória. Quando sai do escopo, o ponteiro é excluído, mas o objeto para o qual ele aponta permanece. Aqui, analisamosdelete
esse objeto antes de sair do escopo para garantir que não haja vazamento de memória. No entanto, também poderíamos ter passado esse ponteiro em outro lugar e esperamos que ele seja excluído mais tarde.Essa natureza do escopo se estende às classes:
Aqui, o mesmo princípio se aplica. Não precisamos nos preocupar
bar
quandoFoo
é excluído. No entantootherBar
, apenas o ponteiro é excluído. SeotherBar
é o único ponteiro válido para qualquer objeto que ele aponte, provavelmente devemos destruí-delete
loFoo
. Este é o conceito de condução por trás da RAIIRAII também é a força motriz típica por trás dos ponteiros inteligentes . Na biblioteca padrão C ++, estes são
std::shared_ptr
,std::unique_ptr
estd::weak_ptr
; embora eu tenha visto e usado outrosshared_ptr
/weak_ptr
implementações que seguem os mesmos conceitos. Para isso, um contador de referência rastreia quantos ponteiros existem para um determinado objeto e automaticamentedelete
o objeto quando não houver mais referências a ele.Além disso, tudo se resume a práticas e disciplina adequadas para um programador garantir que seu código lide com objetos corretamente.
fonte
delete
- é o que eu estava procurando. Impressionante.delete
é chamado automaticamente por ponteiros inteligentes, se você os usar, portanto, considere usá-los sempre que um armazenamento automático não puder ser usado.delete
o código do aplicativo (e a partir do C ++ 14 em diante, o mesmo comnew
), mas use ponteiros inteligentes e RAII para excluir objetos de heap.std::unique_ptr
tipo estd::make_unique
função são a substituição direta e mais simplesnew
edelete
no nível do código do aplicativo.C ++ não tem coleta de lixo.
Os aplicativos C ++ são necessários para descartar seu próprio lixo.
Os programadores de aplicativos C ++ são necessários para entender isso.
Quando eles esquecem, o resultado é chamado de "vazamento de memória".
fonte
new
edelete
.malloc
efree
, ounew[]
edelete[]
, ou quaisquer outros alocadores (como Windows, daGlobalAlloc
,LocalAlloc
,SHAlloc
,CoTaskMemAlloc
,VirtualAlloc
,HeapAlloc
, ...), e memória alocada para você (por exemplo, viafopen
).Em C, C ++ e outros sistemas sem um Garbage Collector, o desenvolvedor recebe recursos da linguagem e de suas bibliotecas para indicar quando a memória pode ser recuperada.
A instalação mais básica é o armazenamento automático . Muitas vezes, o próprio idioma garante que os itens sejam descartados:
Nesses casos, o compilador é responsável por saber quando esses valores não são utilizados e recuperar o armazenamento associado a eles.
Ao usar o armazenamento dinâmico , em C, a memória é tradicionalmente alocada
malloc
e recuperada comfree
. Em C ++, a memória é tradicionalmente alocadanew
e recuperada comdelete
.C não mudou muito ao longo dos anos, no entanto moderno C ++ evita
new
edelete
completamente e se baseia em vez de instalações de biblioteca (que se usanew
edelete
apropriadamente):std::unique_ptr
estd::shared_ptr
std::string
,std::vector
,std::map
, ... gerenciar todo internamente memória alocada dinamicamente transparenteFalando nisso
shared_ptr
, há um risco: se um ciclo de referências for formado, e não quebrado, poderá haver vazamento de memória. Cabe ao desenvolvedor evitar essa situação, a maneira mais simples é evitarshared_ptr
completamente e a segunda mais simples é evitar ciclos no nível de tipo.Como resultado vazamentos de memória não são um problema em C ++ , mesmo para novos usuários, enquanto eles se abstenham de usar
new
,delete
oustd::shared_ptr
. Isso é diferente de C, onde uma disciplina firme é necessária e geralmente insuficiente.No entanto, essa resposta não seria completa sem mencionar a irmã gêmea dos vazamentos de memória: indicadores pendentes .
Um ponteiro pendente (ou referência pendente) é um risco criado ao manter um ponteiro ou referência a um objeto que está morto. Por exemplo:
Usar um ponteiro oscilante ou referência é Comportamento indefinido . Em geral, felizmente, isso é um acidente imediato; muitas vezes, infelizmente, isso causa corrupção de memória primeiro ... e de tempos em tempos surge um comportamento estranho, porque o compilador emite um código realmente estranho.
Comportamento indefinido é o maior problema com C e C ++ até hoje, em termos de segurança / correção de programas. Convém verificar Rust para um idioma sem Garbage Collector e sem comportamento indefinido.
fonte
new
,delete
eshared_ptr
"; semnew
eshared_ptr
você tem propriedade direta, portanto não há vazamentos. Claro, é provável que você tenha ponteiros pendentes, etc ... mas receio que você precise sair do C ++ para se livrar deles.C ++ tem essa coisa chamada RAII . Basicamente, isso significa que o lixo é limpo à medida que você vai, em vez de deixá-lo em uma pilha e deixar o limpador arrumar depois de você. (imagine-me no meu quarto assistindo futebol - enquanto bebo latas de cerveja e preciso de novas, a maneira C ++ é levar a lata vazia para a lixeira a caminho da geladeira, a maneira C # é jogá-la no chão e espere a empregada buscá-los quando ela vier fazer a limpeza).
Agora é possível vazar memória no C ++, mas, para isso, é necessário deixar as construções usuais e reverter para o modo C de fazer as coisas - alocar um bloco de memória e acompanhar onde esse bloco está sem nenhuma assistência de idioma. Algumas pessoas esquecem esse ponteiro e, portanto, não podem remover o bloco.
fonte
Deve-se notar que, no caso do C ++, é um equívoco comum que "você precisa executar o gerenciamento manual de memória". De fato, você geralmente não faz nenhum gerenciamento de memória no seu código.
Objetos de tamanho fixo (com vida útil do escopo)
Na grande maioria dos casos em que você precisa de um objeto, o objeto terá uma vida útil definida em seu programa e é criado na pilha. Isso funciona para todos os tipos de dados primitivos internos, mas também para instâncias de classes e estruturas:
Os objetos de pilha são removidos automaticamente quando a função termina. Em Java, os objetos são sempre criados no heap e, portanto, precisam ser removidos por algum mecanismo como a coleta de lixo. Este não é um problema para objetos de pilha.
Objetos que gerenciam dados dinâmicos (com vida útil do escopo)
Usar espaço na pilha funciona para objetos de tamanho fixo. Quando você precisa de uma quantidade variável de espaço, como uma matriz, outra abordagem é usada: A lista é encapsulada em um objeto de tamanho fixo que gerencia a memória dinâmica para você. Isso funciona porque os objetos podem ter uma função de limpeza especial, o destruidor. É garantido que será chamado quando o objeto sair do escopo e fizer o oposto do construtor:
Não há gerenciamento de memória no código em que a memória é usada. A única coisa que precisamos ter certeza é que o objeto que escrevemos tem um destruidor adequado. Não importa como deixemos o escopo
listTest
, seja por uma exceção ou simplesmente retornando, o destruidor~MyList()
será chamado e não precisamos gerenciar nenhuma memória.(Eu acho que é uma decisão engraçada de design usar o operador binário NOT ,,
~
para indicar o destruidor. Quando usado em números, ele inverte os bits; por analogia, aqui indica que o que o construtor fez é invertido.)Basicamente, todos os objetos C ++ que precisam de memória dinâmica usam esse encapsulamento. Foi chamado RAII ("aquisição de recursos é inicialização"), que é uma maneira bastante estranha de expressar a idéia simples de que os objetos se preocupam com seu próprio conteúdo; o que eles adquirem é deles para limpar.
Objetos polimórficos e vida útil além do escopo
Agora, os dois casos foram para memória com uma vida útil claramente definida: a vida útil é igual ao escopo. Se não queremos que um objeto expire ao sair do escopo, existe um terceiro mecanismo que pode gerenciar a memória para nós: um ponteiro inteligente. Ponteiros inteligentes também são usados quando você tem instâncias de objetos cujo tipo varia em tempo de execução, mas que possuem uma interface ou classe base comum:
Há outro tipo de ponteiro inteligente
std::shared_ptr
, para compartilhar objetos entre vários clientes. Eles apenas excluem seu objeto contido quando o último cliente fica fora do escopo, para que possam ser usados em situações em que é completamente desconhecido quantos clientes haverá e quanto tempo eles usarão o objeto.Em resumo, vemos que você realmente não faz nenhum gerenciamento manual de memória. Tudo é encapsulado e, então, resolvido por meio de um gerenciamento de memória completamente automático e baseado em escopo. Nos casos em que isso não é suficiente, são usados indicadores inteligentes que encapsulam a memória bruta.
É uma prática extremamente ruim usar ponteiros brutos como proprietários de recursos em qualquer lugar do código C ++, alocações brutas fora dos construtores e
delete
chamadas brutas fora dos destruidores, pois são quase impossíveis de gerenciar quando ocorrem exceções e geralmente difíceis de usar com segurança.O melhor: isso funciona para todos os tipos de recursos
Um dos maiores benefícios do RAII é que ele não se limita à memória. Na verdade, fornece uma maneira muito natural de gerenciar recursos, como arquivos e soquetes (abertura / fechamento) e mecanismos de sincronização, como mutexes (bloqueio / desbloqueio). Basicamente, todos os recursos que podem ser adquiridos e devem ser liberados são gerenciados exatamente da mesma maneira em C ++, e nada desse gerenciamento é deixado para o usuário. Tudo é encapsulado em classes que são adquiridas no construtor e liberadas no destruidor.
Por exemplo, uma função que bloqueia um mutex geralmente é escrita assim em C ++:
Outras línguas tornam isso muito mais complicado, exigindo que você faça isso manualmente (por exemplo, em uma
finally
cláusula) ou geram mecanismos especializados que resolvem esse problema, mas não de uma maneira particularmente elegante (geralmente mais tarde na vida, quando há pessoas suficientes). sofria da deficiência). Esses mecanismos são try-with-resources em Java e a instrução using em C #, ambas aproximações do RAII do C ++.Então, para resumir, tudo isso foi um relato muito superficial do RAII em C ++, mas espero que ajude os leitores a entender que a memória e até o gerenciamento de recursos em C ++ não são geralmente "manuais", mas na maioria das vezes automáticos.
fonte
delete
respostas "O C ++ não possui gerenciamento de memória automatizado, esqueça um e você está morto" subindo acima de 30 pontos e sendo aceito, enquanto este tem cinco. Alguém realmente usa C ++ aqui?Com relação ao C especificamente, o idioma não fornece ferramentas para gerenciar a memória alocada dinamicamente. Você é absolutamente responsável por garantir que todos
*alloc
tenham um correspondente emfree
algum lugar.Onde as coisas ficam realmente desagradáveis é quando uma alocação de recursos falha no meio; você tenta de novo, reverte e recomeça desde o início, retrocede e sai com um erro, apenas se livra completamente e deixa o sistema operacional lidar com isso?
Por exemplo, aqui está uma função para alocar uma matriz 2D não contígua. O comportamento aqui é que, se ocorrer uma falha de alocação no meio do processo, reverteremos tudo e retornaremos uma indicação de erro usando um ponteiro NULL:
Esse código é muito feio com esses
goto
s, mas, na ausência de qualquer tipo de mecanismo estruturado de manipulação de exceções, é praticamente a única maneira de lidar com o problema sem apenas sair completamente, especialmente se o código de alocação de recursos estiver aninhado mais de um loop profundo. Este é um dos poucos momentos em quegoto
é realmente uma opção atraente; caso contrário, você está usando um monte de sinalizadores eif
instruções extras .Você pode facilitar sua vida escrevendo funções dedicadas de alocador / desalocador para cada recurso, algo como
fonte
goto
declarações. Esta é uma prática recomendada em algumas áreas. É um esquema comumente usado para proteger contra o equivalente a exceções em C. Dê uma olhada no código do kernel do Linux, que está repleto degoto
instruções - e que não vaza.goto
é estranho. Seria mais legível se você mudassegoto done;
parareturn arr;
earr=NULL;done:return arr;
parareturn NULL;
. Embora, em casos mais complicados, possa realmente haver váriosgoto
s, começando a se desenrolar em diferentes níveis de prontidão (o que seria feito pela exceção da pilha de exceções em C ++).Aprendi a classificar problemas de memória em várias categorias diferentes.
Uma vez pinga. Suponha que um programa vaze 100 bytes no momento da inicialização, apenas para nunca vazar novamente. Perseguir e eliminar esses vazamentos únicos é bom (eu gosto de ter um relatório limpo por um recurso de detecção de vazamentos), mas não é essencial. Às vezes, existem problemas maiores que precisam ser atacados.
Vazamentos repetidos. Uma função chamada repetidamente durante a vida útil de um programa que regularmente vaza memória um grande problema. Esses gotejamentos torturam o programa, e possivelmente o sistema operacional, até a morte.
Referências mútuas. Se os objetos A e B se referenciarem através de ponteiros compartilhados, será necessário fazer algo especial, no design dessas classes ou no código que implementa / usa essas classes para quebrar a circularidade. (Isso não é um problema para idiomas coletados pelo lixo.)
Lembrando demais. Este é o primo do mal do lixo / vazamentos de memória. O RAII não ajudará aqui, nem a coleta de lixo. Este é um problema em qualquer idioma. Se alguma variável ativa tiver um caminho que a conecte a algum pedaço aleatório de memória, esse pedaço aleatório de memória não será lixo. Tornar um programa esquecido para que ele possa ser executado por vários dias é complicado. Criar um programa que possa ser executado por vários meses (por exemplo, até o disco falhar) é muito, muito complicado.
Não tenho um problema sério de vazamento há muito, muito tempo. O uso de RAII em C ++ ajuda bastante a resolver esses vazamentos. (Porém, é preciso ter cuidado com os ponteiros compartilhados.) Muito mais importante: tive problemas com aplicativos cujo uso de memória continua a crescer cada vez mais por causa de conexões indesejadas à memória que não são mais úteis.
fonte
Cabe ao programador C ++ implementar sua própria forma de coleta de lixo, quando necessário. Não fazer isso resultará no que é chamado de 'vazamento de memória'. É bastante comum que linguagens de 'alto nível' (como Java) tenham construído na coleta de lixo, mas linguagens de 'baixo nível', como C e C ++, não.
fonte