Se eu precisar usar um pedaço de memória durante toda a vida útil do meu programa, será realmente necessário liberá-lo imediatamente antes do término do programa?

65

Em muitos livros e tutoriais, ouvi a prática de gerenciamento de memória estressada e senti que algumas coisas misteriosas e terríveis aconteceriam se eu não liberasse memória depois que eu terminasse de usá-la.

Eu não posso falar por outros sistemas (embora para mim seja razoável assumir que eles adotam uma prática similar), mas pelo menos no Windows, o Kernel é basicamente garantido para limpar a maioria dos recursos (com exceção de alguns poucos ) usado por um programa após o término do programa. Que inclui memória heap, entre várias outras coisas.

Eu entendo por que você gostaria de fechar um arquivo depois de usá-lo para disponibilizá-lo ao usuário ou por que você deseja desconectar um soquete conectado a um servidor para economizar largura de banda, mas Parece bobo ter que micromanage toda sua memória usada pelo seu programa.

Agora, concordo que essa pergunta é ampla, já que a maneira como você deve lidar com a memória baseia-se na quantidade de memória necessária e quando é necessária. Por isso, vou reduzir o escopo desta pergunta: Se eu precisa usar um pedaço de memória durante toda a vida útil do meu programa, é realmente necessário liberá-lo logo antes do término do programa?

Editar: A questão sugerida como duplicata era específica da família Unix de sistemas operacionais. Sua principal resposta até especificou uma ferramenta específica para o Linux (por exemplo, Valgrind). Esta questão destina-se a cobrir a maioria dos sistemas operacionais não "incorporados" e por que é ou não uma boa prática liberar memória necessária durante toda a vida de um programa.

    
por CaptainObvious 27.12.2015 / 08:36
fonte

8 respostas

108

If I need to use a piece of memory throughout the lifespan of my program, is it really necessary to free it right before program termination?

Não é obrigatório, mas pode ter benefícios (assim como algumas desvantagens).

Se o programa alocar memória uma vez durante seu tempo de execução e, caso contrário, nunca o liberaria até o término do processo, pode ser uma abordagem sensata não liberar a memória manualmente e confiar no sistema operacional. Em todos os sistemas operacionais modernos que conheço, isso é seguro, no final do processo toda a memória alocada é retornada com segurança ao sistema.
Em alguns casos, não limpar explicitamente a memória alocada pode até ser notavelmente mais rápido do que fazer a limpeza.

No entanto, liberando toda a memória no final da execução explicitamente,

  • durante a depuração / teste, as ferramentas de detecção de vazamento de memos não mostram "falsos positivos"
  • pode ser muito mais fácil mover o código que usa a memória juntamente com alocação e desalocação em um componente separado e usá-lo posteriormente em um contexto diferente, em que o tempo de uso da memória precisa ser controlado pelo usuário do componente

A vida útil dos programas pode mudar. Talvez seu programa seja um utilitário de linha de comando pequeno hoje, com uma vida útil típica de menos de 10 minutos, e aloca memória em partes de alguns kb a cada 10 segundos - portanto, não precisa liberar nenhuma memória alocada antes que o programa termine. Posteriormente, o programa é alterado e recebe um uso prolongado como parte de um processo do servidor com várias semanas de vida - portanto, não liberar memória não utilizada não é mais uma opção; caso contrário, seu programa começará a consumir toda a memória disponível no servidor ao longo do tempo . Isso significa que você terá que revisar o programa inteiro e adicionar o código de desalocação posteriormente. Se você tiver sorte, esta é uma tarefa fácil, se não, pode ser tão difícil que as chances são altas que você perca um lugar. E quando você estiver nessa situação, você desejará ter adicionado o código "gratuito" ao seu programa antecipadamente, no momento em que você adicionou o código "malloc".

Mais geralmente, escrever alocação e código de desalocação relacionado sempre em par conta como um "bom hábito" entre muitos programadores: fazendo isso sempre, você diminui a probabilidade de esquecer o código de desalocação em situações onde a memória deve ser libertado.

    
por 27.12.2015 / 09:36
fonte
11

Liberar memória no final de uma execução de programas é apenas um desperdício de tempo de CPU. É como arrumar uma casa antes de tirá-la de órbita.

No entanto, às vezes, o que era um programa de execução curta pode se tornar parte de um programa de execução muito mais longa. Então, liberar as coisas se torna necessário. Se isso não foi pensado, pelo menos até certo ponto, então pode envolver um retrabalho substancial.

Uma solução inteligente para isso é "talloc", que permite fazer uma carga de alocações de memória e, em seguida, descartá-las com uma única chamada.

    
por 27.12.2015 / 16:10
fonte
5

Você pode usar um idioma com coleta de lixo (como Scheme, Ocaml, Haskell, Common Lisp e até mesmo Java, Scala, Clojure).

(Na maioria dos idiomas GC-ed, não há nenhuma maneira para explicitamente e manualmente liberam memória! Às vezes, alguns valores pode ser finalizado , por exemplo, o GC e o sistema de tempo de execução fecha um valor de identificador de arquivo quando esse valor é inacessível, mas isso não é certo, não confiável, e você deve fechar explicitamente suas identificações de arquivo, já que a finalização nunca é garantido)

Você também pode, para o seu programa codificado em C (ou mesmo em C ++), usar o coletor de lixo conservador de Boehm . Você substituiria todo o seu malloc com GC_malloc e não se incomodaria com free -ing de qualquer ponteiro. É claro que você precisa entender os prós e contras de usar o GC de Boehm. Leia também o manual do GC .

Gerenciamento de memória é uma propriedade global de um programa. De alguma forma, (e a vivacidade de alguns dados dados) é não-composicional e não-modular, desde uma propriedade inteira do programa.

Por fim, como outros salientaram, explicitamente free -executar a zona de memória C alocada do seu heap é uma boa prática. Para um programa de brinquedos não alocar muita memória, você poderia até mesmo decidir não free memória (desde quando o process terminou, seus recursos, incluindo seu espaço de endereço virtual , seriam liberados pelo sistema operacional) .

If I need to use a piece of memory throughout the lifespan of my program, is it really necessary to free it right before program termination?

Não, você não precisa fazer isso. E muitos programas do mundo real não se incomodam em liberar alguma memória que é necessária em toda a vida útil (em particular, o compilador GCC não está liberando da sua memória). No entanto, quando você faz isso (por exemplo, você não se incomoda em free -em alguma parte específica de dados dinamicamente alocados ), é melhor comentar esse fato para facilitar o trabalho de futuros programadores no mesmo projeto. Eu costumo recomendar que a quantidade de memória unfreed permaneça limitada, e geralmente relativamente pequena, w.r.t. para totalizar a memória de heap usada.

Observe que o sistema free geralmente não libera memória para o sistema operacional (por exemplo, chamando munmap (2) em sistemas POSIX) mas normalmente marca uma zona de memória como reutilizável no futuro malloc . Em particular, o espaço de endereço virtual (por exemplo, visto através de /proc/self/maps no Linux, veja proc (5) ) ....) pode não encolher após free (portanto, utilitários como ps ou top estão relatando a mesma quantidade de memória usada para o seu processo).

    
por 27.12.2015 / 11:32
fonte
3

Não é necessário , pois você não falhará em executar o programa corretamente se não conseguir. No entanto, existem razões que você pode escolher, se for dada uma chance.

Um dos casos mais poderosos em que me deparo (mais e mais) é que alguém escreve um pequeno pedaço de código de simulação que é executado em seu executável. Eles dizem "nós gostaríamos que este código fosse integrado à simulação". Então, pergunto-lhes como planejam reinicializar as corridas de monte-carlo e olham para mim sem expressão. "O que quer dizer reinicializar? Você acabou de executar o programa com novas configurações?"

Às vezes, uma limpeza limpa torna muito mais fácil para o seu software ser usado. No caso de muitos exemplos, você presume que nunca precisa limpar algo, e faz suposições sobre como você pode lidar com os dados e sua vida em torno desses pressupostos. Quando você muda para um novo ambiente onde essas pressuposições não são válidas, algoritmos inteiros podem não funcionar mais.

Para um exemplo de como as coisas podem ficar estranhas, veja como as linguagens gerenciadas lidam com a finalização no final dos processos, ou como o C # lida com a interrupção programática de Application Domains. Eles se amarram em nós porque há suposições que caem nas rachaduras nesses casos extremos.

    
por 27.12.2015 / 21:55
fonte
1

Ignorando idiomas nos quais você não libera a memória manualmente ...

O que você pensa como "um programa" agora pode em algum momento tornar-se apenas uma função ou método que faz parte de um programa maior. E então essa função pode ser chamada várias vezes. E então a memória que você deveria ter "liberado manualmente" será um vazamento de memória. Isso é claro, um julgamento.

    
por 27.12.2015 / 14:33
fonte
1

Porque é muito provável que em algum momento você deseje alterar seu programa, talvez integrá-lo com outra coisa, execute várias instâncias em sequência ou em paralelo. Então, será necessário liberar manualmente essa memória, mas você não se lembrará mais das circunstâncias e custará muito mais tempo para você entender seu programa.

Faça coisas enquanto seu entendimento sobre elas ainda estiver fresco.

É um pequeno investimento que pode gerar grandes retornos no futuro.

    
por 28.12.2015 / 11:05
fonte
1

Não, não é necessário, mas é uma boa ideia.

Você diz que acha que "coisas terríveis e misteriosas aconteceriam se eu não liberasse memória depois que eu terminasse de usá-la."

Em termos técnicos, a única conseqüência disso é que seu programa continuará comendo mais memória até que um limite rígido seja atingido (por exemplo, seu espaço de endereço virtual esteja esgotado) ou o desempenho se torne inaceitável. Se o programa estiver prestes a sair, nada disso é importante porque o processo deixa de existir. As "coisas misteriosas e terríveis" são puramente sobre o estado mental do desenvolvedor. Encontrar a fonte de um vazamento de memória pode ser um pesadelo absoluto (isso é um eufemismo) e é preciso um lote de habilidade e disciplina para escrever código livre de vazamentos. A abordagem recomendada para desenvolver essa habilidade e disciplina é sempre liberar memória quando não for mais necessária, mesmo que o programa esteja prestes a terminar.

É claro que isso tem a vantagem de que seu código pode ser reutilizado e adaptado mais facilmente, como outros já disseram.

No entanto , há pelo menos um caso em que é melhor não liberar memória antes do término do programa.

Considere o caso em que você fez milhões de pequenas alocações e elas foram em grande parte trocadas para o disco. Quando você começa a liberar tudo, a maior parte de sua memória precisa ser trocada de volta para a RAM para que as informações de contabilidade possam ser acessadas, apenas para descartar imediatamente os dados. Isso pode fazer com que o programa demore alguns minutos apenas para sair! Para não mencionar colocar muita pressão sobre o disco e memória física durante esse tempo. Se a memória física é escassa para começar (talvez o programa esteja sendo fechado porque outro programa está mascando muita memória), então páginas individuais podem precisar ser trocadas várias vezes quando vários objetos precisam ser liberados da memória. mesma página, mas não são liberados consecutivamente.

Se, em vez disso, o programa simplesmente abortar, o SO simplesmente descartará toda a memória que foi trocada para o disco, o que é quase instantâneo, porque não requer nenhum acesso ao disco.

É importante notar que, em uma linguagem OO, chamar o destrutor de um objeto também forçará a troca da memória; se você tiver que fazer isso, você pode liberar a memória.

    
por 28.12.2015 / 06:17
fonte
0

Os principais motivos para limpar manualmente são: você tem menos probabilidade de confundir um vazamento de memória genuíno por um bloco de memória que será limpo na saída. Às vezes, você só detectará bugs no alocador quando percorrer todo o processo. estrutura de dados para desalocá-lo, e você terá uma dor de cabeça muito menor se você alguma vez refatorar para que algum objeto precise ser desalocado antes que o programa saia.

Os principais motivos para não limpar manualmente são: desempenho, desempenho, desempenho e a possibilidade de algum tipo de erro de duas ou de duas sem uso em seu código de limpeza desnecessário, que pode se transformar em uma falha bug ou exploração de segurança.

Se você se preocupa com o desempenho, sempre deseja criar um perfil para descobrir onde está perdendo todo o seu tempo, em vez de adivinhar. Um compromisso possível é envolver o código de limpeza opcional em um bloco condicional, deixá-lo no momento da depuração para que você obtenha os benefícios de escrever e depurar o código e, em seguida, se e somente se tiver determinado empiricamente que é demais sobrecarga, diga ao compilador para pulá-lo em seu executável final. E um atraso no fechamento do programa é, quase por definição, quase nunca no caminho crítico.

    
por 28.12.2015 / 04:16
fonte