O que REALMENTE acontece quando você não libera o malloc?

Isso tem sido algo que me incomoda há muito tempo.

Nós somos todos ensinados na escola (pelo menos, eu era) que você deve libertar cada ponteiro que é atribuído. Estou um pouco curioso, no entanto, sobre o custo real de não liberar memory. Em alguns casos óbvios, como quando malloc é chamado dentro de um loop ou parte de uma execução de thread, é muito importante liberar para que não haja vazamentos de memory. Mas considere os dois exemplos a seguir:

Primeiro, se eu tiver código, é algo assim:

 int main() { char *a = malloc(1024); /* Do some arbitrary stuff with 'a' (no alloc functions) */ return 0; } 

Qual é o resultado real aqui? Meu pensamento é que o processo morre e, em seguida, o espaço do heap desapareceu de qualquer maneira, não há mal algum em perder o chamado para free (no entanto, reconheço a importância de tê-lo de qualquer maneira para fechamento, manutenção e boas práticas). Estou certo nesse pensamento?

Segundo, digamos que eu tenha um programa que funcione um pouco como um shell. Os usuários podem declarar variables ​​como aaa = 123 e elas são armazenadas em alguma estrutura de dados dinâmica para uso posterior. Claramente, parece óbvio que você usaria alguma solução que chame alguma function de alocação (hashmap, lista encadeada, algo assim). Para este tipo de programa, não faz sentido libertar-se depois de chamar o malloc porque estas variables ​​devem estar presentes em todos os momentos durante a execução do programa e não há uma boa maneira (que eu possa ver) de implementar isto com espaço alocado estaticamente. É um projeto ruim ter um monte de memory alocada mas liberada apenas como parte do processo que termina? Se sim, qual é a alternativa?

   

Quase todos os sistemas operacionais modernos recuperam todo o espaço de memory alocado após a saída de um programa. A única exceção que posso imaginar pode ser algo como o Palm OS, onde o armazenamento estático e a memory de tempo de execução do programa são praticamente a mesma coisa, portanto, não liberar isso pode fazer com que o programa ocupe mais espaço de armazenamento. (Eu estou apenas especulando aqui.)

Então, geralmente, não há nenhum dano nisso, exceto o custo de tempo de execução de ter mais armazenamento do que você precisa. Certamente, no exemplo que você dá, você quer manter a memory para uma variável que pode ser usada até que seja limpa.

No entanto, é considerado um bom estilo para liberar memory assim que você não precisar mais dela, e para liberar qualquer coisa que ainda exista na saída do programa. É mais um exercício de saber qual memory você está usando e pensar se você ainda precisa dela. Se você não acompanhar, poderá haver vazamentos de memory.

Por outro lado, a advertência semelhante para fechar seus arquivos na saída tem um resultado muito mais concreto – se você não fizer isso, os dados que você escreveu para eles podem não ficar liberados, ou se eles são um arquivo temporário, eles podem não seja deletado quando estiver pronto. Além disso, as alças do database devem ter suas transactions confirmadas e, em seguida, fechadas quando você terminar com elas. Da mesma forma, se você estiver usando uma linguagem orientada a object como C ++ ou Objective C, não liberar um object quando terminar, significa que o destruidor nunca será chamado, e quaisquer resources que a class seja responsável podem não ser limpos.

Sim, você está certo, seu exemplo não causa nenhum dano (pelo menos não nos sistemas operacionais mais modernos). Toda a memory alocada pelo seu processo será recuperada pelo sistema operacional assim que o processo terminar.

Fonte: Allocation and GC Myths (alerta de PostScript!)

Alocação Mito 4: Os programas não coletados de lixo devem sempre desalocar toda a memory que alocam.

A verdade: Desalocações omitidas no código executado com frequência causam vazamentos crescentes. Eles raramente são aceitáveis. mas os programas que retêm a maior parte da memory alocada até a saída do programa geralmente têm um desempenho melhor sem qualquer desalocação de intervenção. O Malloc é muito mais fácil de implementar se não houver liberdade.

Na maioria dos casos, desalocar a memory antes da saída do programa é inútil. O sistema operacional irá recuperá-lo de qualquer maneira. Livre toque e pague nos objects mortos; o sistema operacional não.

Consequência: Tenha cuidado com os “detectores de vazamentos” que contam as alocações. Alguns “vazamentos” são bons!

Dito isto, você deve realmente tentar evitar todos os vazamentos de memory!

Segunda pergunta: seu design está ok. Se você precisar armazenar algo até que seu aplicativo seja encerrado, é recomendável fazer isso com a alocação de memory dinâmica. Se você não souber o tamanho inicial necessário, não poderá usar a memory alocada estaticamente.

=== E quanto a provas futuras e reutilização de código ? ===

Se você não escreve o código para liberar os objects, então você está limitando o código a ser apenas seguro de usar quando você pode depender de a memory ser liberada pelo processo sendo fechado … ou seja, um pequeno uso único projetos ou projetos “descartáveis” [1] … onde você sabe quando o processo terminará.

Se você escrever o código que libera toda a sua memory alocada dinamicamente, então você estará testando o código no futuro e permitindo que outras pessoas o utilizem em um projeto maior.


[1] sobre projetos “descartáveis”. O código usado em projetos “Jogue fora” tem um jeito de não ser jogado fora. A próxima coisa que você sabe é que dez anos se passaram e seu código “descartável” ainda está sendo usado.

Eu ouvi uma história sobre um cara que escreveu algum código apenas por diversão para fazer seu hardware funcionar melhor. Ele disse ” apenas um hobby, não será grande e profissional “. Anos depois, muitas pessoas estão usando seu código “hobby”.

Você está correto, nenhum dano é feito e é mais rápido apenas sair

Há várias razões para isto:

  • Todos os ambientes de desktop e servidor simplesmente liberam todo o espaço da memory na saída (). Eles não têm conhecimento das estruturas de dados internas do programa, como heaps.

  • Quase todas as implementações free() nunca retornam a memory para o sistema operacional.

  • Mais importante, é um desperdício de tempo quando feito logo antes da saída (). Na saída, as páginas de memory e o espaço de troca são liberados simplesmente. Por outro lado, uma série de chamadas free () queima o tempo de CPU e pode resultar em operações de paginação de disco, falhas de cache e despejos de cache.

Quanto à possibilidade de reutilização de código futuro, justificando a certeza de operações sem sentido: isso é uma consideração, mas não é, indiscutivelmente, o modo Ágil . YAGNI!

Eu normalmente libero todos os blocos alocados, uma vez que tenho certeza de que termino com isso. Hoje, o ponto de input do meu programa pode ser main(int argc, char *argv[]) , mas amanhã pode ser foo_entry_point(char **args, struct foo *f) e typescript como um ponteiro de function.

Então, se isso acontecer, agora tenho um vazamento.

Em relação à sua segunda pergunta, se meu programa fizesse uma input como a = 5, eu alocaria espaço para a, ou realocaria o mesmo espaço em um a = “foo” subsequente. Isso permaneceria alocado até:

  1. O usuário digitou ‘unset a’
  2. Minha function de limpeza foi inserida, seja servindo um sinal ou o usuário digitou ‘quit’

Eu não consigo pensar em qualquer sistema operacional moderno que não recupere a memory após um processo sair. Então, novamente, free () é barato, porque não limpar? Como outros já disseram, ferramentas como o valgrind são ótimas para detectar vazamentos com os quais você realmente precisa se preocupar. Mesmo que os blocos de seu exemplo sejam rotulados como “ainda acessíveis”, é apenas um ruído extra na saída quando você está tentando garantir que não há vazamentos.

Outro mito é ” Se estiver em main (), não tenho que liberá-lo “, isso é incorreto. Considere o seguinte:

 char *t; for (i=0; i < 255; i++) { t = strdup(foo->name); let_strtok_eat_away_at(t); } 

Se isso veio antes de forking / daemonizing (e, em teoria, funcionando para sempre), seu programa acaba de vazar um tamanho indeterminado de t 255 vezes.

Um bom programa bem escrito deve sempre limpar depois de si. Libere toda a memory, libere todos os arquivos, feche todos os descritores, desvincule todos os arquivos temporários, etc. Esta function de limpeza deve ser alcançada após o término normal, ou ao receber vários tipos de sinais fatais, a menos que você queira deixar alguns arquivos em volta. detectar uma falha e continuar.

Realmente, seja gentil com a pobre alma que tem que manter as suas coisas quando você se move para outras coisas .. entregue a elas ‘valgrind clean’ 🙂

Eu discordo completamente de todos que dizem que o OP está correto ou não há danos.

Todo mundo está falando de um sistema operacional moderno e / ou legado.

Mas e se eu estiver em um ambiente onde simplesmente não tenho sistema operacional? Onde não há nada?

Imagine agora que você está usando interrupções de estilo de linha e aloca memory. No padrão C ISO / IEC: 9899 é o tempo de vida da memory declarado como:

7.20.3 Funções de gerenciamento de memory

1 A ordem e a contiguidade do armazenamento alocado por chamadas sucessivas às funções calloc, malloc e realloc não são especificadas. O ponteiro retornado se a alocação tiver êxito é alinhado adequadamente para que ele possa ser atribuído a um ponteiro para qualquer tipo de object e, em seguida, usado para acessar um object ou uma matriz de tais objects no espaço alocado (até que o espaço seja desalocado explicitamente) . O tempo de vida de um object alocado se estende da alocação até a desalocação. […]

Portanto, não é preciso dizer que o ambiente está fazendo o trabalho de liberação para você. Caso contrário, seria adicionado à última sentença: “Ou até o programa terminar”.

Então, em outras palavras: não liberar memory não é apenas uma má prática. Produz código não portátil e não em conformidade com C. Que pelo menos pode ser visto como “correto, se o seguinte: […] for suportado pelo ambiente”.

Mas nos casos em que você não tem nenhum sistema operacional, ninguém está fazendo o trabalho para você (eu sei que geralmente você não aloca e realoca a memory em sistemas embarcados, mas há casos em que você pode querer.)

Então, falando em geral simples C (como o OP é marcado), isso é simplesmente produzir código errôneo e não portátil.

É completamente bom deixar a memory sem correção quando você sai; malloc () aloca a memory da área de memory chamada “a pilha”, e a pilha completa de um processo é liberada quando o processo é encerrado.

Dito isto, uma razão pela qual as pessoas ainda insistem que é bom liberar tudo antes de sair é que os depuradores de memory (por exemplo, valgrind no Linux) detectam os blocos não revelados como vazamentos de memory, e se você também tem vazamentos “reais” de memory, mais difícil identificá-los se você também obtiver resultados “falsos” no final.

Se você está usando a memory que você alocou, então você não está fazendo nada errado. Ele se torna um problema quando você escreve funções (diferentes de main) que alocam memory sem liberá-las e sem disponibilizá-las para o resto do programa. Então seu programa continua rodando com aquela memory alocada para ele, mas não há como usá-lo. Seu programa e outros programas em execução são privados dessa memory.

Edit: Não é 100% preciso dizer que outros programas em execução são privados dessa memory. O sistema operacional sempre pode deixá-los usá-lo à custa de trocar seu programa para a memory virtual ( ). O ponto é, porém, que se o seu programa libera memory que não está usando, então é menos provável que uma troca de memory virtual seja necessária.

Esse código geralmente funciona bem, mas considere o problema de reutilização de código.

Você pode ter escrito algum trecho de código que não libera memory alocada, ele é executado de tal forma que a memory é automaticamente recuperada. Parece tudo bem.

Então alguém copia seu trecho em seu projeto de tal forma que ele é executado mil vezes por segundo. Essa pessoa agora tem um enorme memory leaks em seu programa. Não é muito bom em geral, geralmente fatal para um aplicativo de servidor.

A reutilização de código é típica nas empresas. Normalmente, a empresa possui todo o código que seus funcionários produzem e todos os departamentos podem reutilizar o que a empresa possui. Então, ao escrever esse código de “aparência inocente”, você pode causar dor de cabeça em potencial a outras pessoas. Isso pode fazer com que você seja demitido.

Qual é o resultado real aqui?

Seu programa vazou a memory. Dependendo do seu sistema operacional, ele pode ter sido recuperado.

A maioria dos sistemas operacionais de desktop modernos recuperam a memory vazada na finalização do processo, tornando, infelizmente, comum ignorar o problema, como pode ser visto por muitas outras respostas aqui.

Mas você está confiando em um recurso de segurança no qual não deve confiar, e seu programa (ou function) pode ser executado em um sistema em que esse comportamento resulta em um memory leaks “difícil”, na próxima vez.

Você pode estar executando no modo kernel ou em sistemas operacionais antigos / incorporados que não utilizam proteção de memory como compensação. (MMUs ocupam espaço, a proteção de memory custa ciclos adicionais de CPU, e não é pedir muito de um programador para limpar depois de si mesmo).

Você pode usar e reutilizar a memory da maneira que preferir, mas certifique-se de desalocar todos os resources antes de sair.

Não há nenhum perigo real em não liberar suas variables, mas se você atribuir um ponteiro a um bloco de memory a um bloco diferente de memory sem liberar o primeiro bloco, o primeiro bloco não estará mais acessível, mas ainda ocupará espaço. Isso é o que chamamos de memory leaks, e se você fizer isso com regularidade, seu processo começará a consumir mais e mais memory, tirando resources do sistema de outros processos.

Se o processo é de curta duração, muitas vezes você pode fazer isso, pois toda a memory alocada é recuperada pelo sistema operacional quando o processo é concluído, mas aconselho adquirir o hábito de liberar toda a memory para a qual você não tem mais uso.

Você está correto, a memory é liberada automaticamente quando o processo é encerrado. Algumas pessoas se esforçam para não fazer uma limpeza extensiva quando o processo é encerrado, uma vez que tudo será liberado para o sistema operacional. No entanto, enquanto o seu programa está em execução, você deve liberar memory não usada. Se não o fizer, você pode eventualmente acabar ou causar paginação excessiva se o seu conjunto de trabalho ficar muito grande.

Se você está desenvolvendo uma aplicação do zero, você pode fazer algumas escolhas informadas sobre quando ligar gratuitamente. Seu programa de exemplo é bom: ele aloca memory, talvez você trabalhe por alguns segundos e então fecha, liberando todos os resources reivindicados.

Se você está escrevendo alguma outra coisa, no entanto – um servidor / aplicativo de longa duração, ou uma biblioteca para ser usada por outra pessoa, você deve esperar chamar de graça tudo o que você malloc.

Ignorando o lado pragmático por um segundo, é muito mais seguro seguir a abordagem mais rigorosa e forçar-se a libertar tudo o que você malloc. Se você não tem o hábito de procurar por vazamentos de memory sempre que codifica, pode facilmente provocar alguns vazamentos. Então, em outras palavras, sim – você pode fugir sem isso; Por favor, tenha cuidado, no entanto.

Você está absolutamente correto a esse respeito. Em pequenos programas triviais onde uma variável deve existir até a morte do programa, não há benefício real para desalocar a memory.

Na verdade, eu já estive envolvido em um projeto em que cada execução do programa era muito complexa, mas relativamente curta, e a decisão era apenas manter a memory alocada e não desestabilizar o projeto, cometendo erros ao desalocá-lo.

Dito isto, na maioria dos programas isso não é realmente uma opção, ou pode levar você a ficar sem memory.

Na verdade, há uma seção no manual on-line do OSTEP para um curso de graduação em sistemas operacionais que discute exatamente a sua pergunta.

A seção relevante é “Esquecendo a memory livre” no capítulo API de memory na página 6 que fornece a seguinte explicação:

Em alguns casos, pode parecer que não chamar free () é razoável. Por exemplo, seu programa é de curta duração e logo será encerrado; Nesse caso, quando o processo morrer, o sistema operacional limpará todas as páginas alocadas e, portanto, nenhum memory leaks ocorrerá por si só. Enquanto isso certamente “funciona” (veja o apartado na página 7), é provavelmente um mau hábito de se desenvolver, então seja cuidadoso ao escolher tal estratégia.

Este trecho está no contexto de introduzir o conceito de memory virtual. Basicamente, neste ponto do livro, os autores explicam que um dos objectives de um sistema operacional é “virtualizar a memory”, isto é, permitir que cada programa acredite que tem access a um espaço de endereçamento de memory muito grande.

Nos bastidores, o sistema operacional traduzirá “endereços virtuais” que o usuário vê para os endereços reais que apontam para a memory física.

No entanto, o compartilhamento de resources, como memory física, exige que o sistema operacional monitore quais processos estão sendo usados. Portanto, se um processo é encerrado, os resources e os objectives do projeto do sistema operacional são recuperados para recuperar a memory do processo, de modo que ela possa redistribuir e compartilhar a memory com outros processos.


EDIT: O aparte mencionado no excerto é copiado abaixo.

TANTO: POR QUE NENHUMA MEMÓRIA É SOB AQUELE QUE SEU PROCESSO SAI

Quando você escreve um programa de curta duração, você pode alocar algum espaço usando malloc() . O programa é executado e está prestes a ser concluído: há necessidade de chamar free() várias vezes antes de sair? Embora pareça errado não, nenhuma memory será “perdida” em qualquer sentido real. O motivo é simples: existem realmente dois níveis de gerenciamento de memory no sistema. O primeiro nível de gerenciamento de memory é executado pelo sistema operacional, que distribui a memory para os processos quando eles são executados e os recupera quando os processos saem (ou morrem). O segundo nível de gerenciamento está dentro de cada processo, por exemplo, dentro do heap quando você chama malloc() e free() . Mesmo se você não chamar free() (e, portanto, vazar memory no heap), o sistema operacional recuperará toda a memory do processo (incluindo as páginas de código, pilha e, conforme relevante aqui, heap) quando o programa terminou a execução. Não importa o estado do seu heap em seu espaço de endereço, o sistema operacional recupera todas essas páginas quando o processo é interrompido, garantindo, assim, que nenhuma memory seja perdida, apesar do fato de você não tê-la liberado.

Assim, para programas de curta duração, vazamentos de memory geralmente não causam problemas operacionais (embora possam ser considerados de má qualidade). Quando você escreve um servidor de longa duração (como um servidor da Web ou sistema de gerenciamento de database, que nunca sai), a memory que vazou é um problema muito maior e acabará levando a uma falha quando o aplicativo ficar sem memory. E, claro, vazar memory é um problema ainda maior dentro de um programa em particular: o próprio sistema operacional. Mostrando-nos mais uma vez: quem escreve o código do kernel tem o trabalho mais difícil de todos …

da página 7 do capítulo API de memory do

Sistemas Operacionais: Três Peças Fáceis
Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau Arpaci-Dusseau Books March, 2015 (Version 0.90)

If a program forgets to free a few Megabytes before it exits the operating system will free them. But if your program runs for weeks at a time and a loop inside the program forgets to free a few bytes in each iteration you will have a mighty memory leak that will eat up all the available memory in your computer unless you reboot it on a regular basis => even small memory leaks might be bad if the program is used for a seriously big task even if it originally wasn’t designed for one.

I think that your two examples are actually only one: the free() should occur only at the end of the process, which as you point out is useless since the process is terminating.

In you second example though, the only difference is that you allow an undefined number of malloc() , which could lead to running out of memory. The only way to handle the situation is to check the return code of malloc() and act accordingly.