Estou estudando engenharia da computação e tenho alguns cursos de eletrônica. Ouvi, de dois de meus professores (destes cursos) que é possível evitar o uso da free()
função (depois malloc()
, calloc()
etc.) porque os espaços de memória alocados provavelmente não será usado novamente para alocar outra memória. Ou seja, por exemplo, se você alocar 4 bytes e depois liberá-los, terá 4 bytes de espaço que provavelmente não serão alocados novamente: você terá um buraco .
Eu acho isso loucura: você não pode ter um programa não-brinquedo no qual você aloca memória no heap sem liberá-la. Mas não tenho o conhecimento para explicar exatamente por que é tão importante que para cada um malloc()
deve haver um free()
.
Portanto: existem circunstâncias em que pode ser apropriado usar um malloc()
sem usar free()
? E se não, como posso explicar isso aos meus professores?
fonte
Respostas:
Fácil: basta ler o código-fonte de praticamente qualquer
malloc()/free()
implementação meio séria . Com isso, quero dizer o gerenciador de memória real que controla o trabalho das chamadas. Isso pode ser na biblioteca de tempo de execução, máquina virtual ou sistema operacional. Claro que o código não é igualmente acessível em todos os casos.Certificar-se de que a memória não está fragmentada, juntando orifícios adjacentes em orifícios maiores, é muito comum. Alocadores mais sérios usam técnicas mais sérias para garantir isso.
Então, vamos supor que você faça três alocações e desalocações e obtenha os blocos dispostos na memória nesta ordem:
+-+-+-+ |A|B|C| +-+-+-+
Os tamanhos das alocações individuais não importam. então você libera o primeiro e o último, A e C:
+-+-+-+ | |B| | +-+-+-+
quando você finalmente libera B, você (inicialmente, pelo menos em teoria) acaba com:
+-+-+-+ | | | | +-+-+-+
que pode ser fragmentado em apenas
+-+-+-+ | | +-+-+-+
ou seja, um único bloco livre maior, sem fragmentos restantes.
Referências, conforme solicitado:
heap4.c
código em FreeRTOS .fonte
As outras respostas já explicam perfeitamente bem que as implementações reais de
malloc()
efree()
realmente aglutinam (desfragmentam) buracos em pedaços livres maiores. Mas mesmo que não fosse o caso, ainda seria uma má ideia renunciarfree()
.O fato é que seu programa acabou de alocar (e quer liberar) esses 4 bytes de memória. Se ele for executado por um longo período de tempo, é bem provável que precise alocar apenas 4 bytes de memória novamente. Portanto, mesmo que esses 4 bytes nunca se aglutinem em um espaço contíguo maior, eles ainda podem ser reutilizados pelo próprio programa.
fonte
free
está sendo chamado o suficiente para ter um impacto no desempenho, provavelmente também está sendo chamado o suficiente, de forma que deixá-lo de fora fará uma grande redução na memória disponível. É difícil imaginar uma situação em um sistema embarcado em que o desempenho sofra continuamente devido a,free
mas ondemalloc
seja chamado apenas um número finito de vezes; é um caso de uso bastante raro ter um dispositivo embutido que faz um processamento único de dados e depois reinicia.É um total absurdo, por exemplo, existem muitas implementações diferentes de
malloc
, algumas tentam tornar o heap mais eficiente como o de Doug Lea ou este .fonte
Seus professores estão trabalhando com POSIX, por acaso? Se eles estão acostumados a escrever muitos aplicativos de shell pequenos e minimalistas, esse é um cenário onde posso imaginar que essa abordagem não seria tão ruim - liberar todo o heap de uma vez no lazer do sistema operacional é mais rápido do que liberar um mil variáveis. Se você espera que seu aplicativo seja executado por um ou dois segundos, pode facilmente se safar sem nenhuma desalocação.
Ainda é uma prática ruim, é claro (as melhorias de desempenho devem sempre ser baseadas em perfis, não em um sentimento vago), e não é algo que você deva dizer aos alunos sem explicar as outras restrições, mas posso imaginar um monte de minúsculo-piping-shell -aplicações a serem escritas desta forma (se não usando alocação estática completa). Se você está trabalhando em algo que se beneficia de não liberar suas variáveis, ou você está trabalhando em condições de latência extremamente baixa (nesse caso, como você pode pagar a alocação dinâmica e C ++?: D), ou você está fazendo algo muito, muito errado (como alocar um array de inteiros alocando mil inteiros um após o outro ao invés de um único bloco de memória).
fonte
Você mencionou que eles eram professores de eletrônica. Eles podem ser usados para escrever firmware / software em tempo real, sendo capazes de cronometrar com precisão algumas coisas que a execução é freqüentemente necessária. Nesses casos, saber que você tem memória suficiente para todas as alocações e não liberar e realocar a memória pode fornecer um limite calculado mais facilmente no tempo de execução.
Em alguns esquemas, a proteção de memória de hardware também pode ser usada para garantir que a rotina seja concluída em sua memória alocada ou gere um trap em casos muito excepcionais.
fonte
malloc
nada nesse caso, em vez disso, contando com a alocação estática (ou talvez alocar um grande pedaço e, em seguida, manipular manualmente a memória).Levando isso de um ângulo diferente dos comentários e respostas anteriores, uma possibilidade é que seus professores tenham experiência com sistemas em que a memória foi alocada estaticamente (ou seja: quando o programa foi compilado).
A alocação estática ocorre quando você faz coisas como:
define MAX_SIZE 32 int array[MAX_SIZE];
Em muitos sistemas de tempo real e incorporados (aqueles com maior probabilidade de serem encontrados por EEs ou CEs), geralmente é preferível evitar completamente a alocação de memória dinâmica. Portanto, os usos de
malloc
,new
e suas contrapartes de exclusão são raros. Além disso, a memória dos computadores explodiu nos últimos anos.Se você tiver 512 MB disponíveis e alocar estaticamente 1 MB, terá aproximadamente 511 MB para percorrer antes que o software exploda (bem, não exatamente ... mas me acompanhe aqui). Supondo que você tenha 511 MB para abusar, se você malloc 4 bytes a cada segundo sem liberá-los, poderá executar por quase 73 horas antes de ficar sem memória. Considerando que muitas máquinas são desligadas uma vez por dia, isso significa que seu programa nunca ficará sem memória!
No exemplo acima, o vazamento é de 4 bytes por segundo, ou 240 bytes / min. Agora imagine que você reduziu a proporção byte / min. Quanto menor a proporção, mais tempo o programa pode ser executado sem problemas. Se seus
malloc
s não forem frequentes, essa é uma possibilidade real.Caramba, se você sabe que está indo para
malloc
algo apenas uma vez, e quemalloc
nunca será atingido novamente, então é muito parecido com a alocação estática, embora você não precise saber o tamanho do que você está alocando- frente. Ex .: Digamos que temos 512 MB novamente. Precisamos demalloc
32 arrays de inteiros. Estes são inteiros típicos - 4 bytes cada. Sabemos que os tamanhos dessas matrizes nunca excederão 1024 inteiros. Nenhuma outra alocação de memória ocorre em nosso programa. Temos memória suficiente? 32 * 1024 * 4 = 131.072. 128 KB - então sim. Temos muito espaço. Se soubermos que nunca iremos alocar mais memória, podemos com segurançamalloc
essas matrizes sem liberá-los. No entanto, isso também pode significar que você precisa reiniciar a máquina / dispositivo se o programa travar. Se você iniciar / parar seu programa 4.096 vezes, alocará todos os 512 MB. Se você tiver processos zumbis, é possível que a memória nunca seja liberada, mesmo após um travamento.Salve-se da dor e da miséria e consuma este mantra como A Verdade Única:
malloc
deve sempre ser associado a afree
.new
deve sempre ter umdelete
.fonte
sizeof(int)
igual a 2 em vez de 4.Eu acho que a afirmação feita na pergunta é absurda se tomada literalmente do ponto de vista do programador, mas ela tem verdade (pelo menos algumas) do ponto de vista do sistema operacional.
malloc () acabará chamando mmap () ou sbrk (), que irá buscar uma página do sistema operacional.
Em qualquer programa não trivial, as chances de que esta página seja devolvida ao sistema operacional durante o tempo de vida de um processo são muito pequenas, mesmo se você liberar () a maior parte da memória alocada. Portanto, a memória free () 'd estará disponível apenas para o mesmo processo na maioria das vezes, mas não para outros.
fonte
Seus professores não estão errados, mas também estão (eles são pelo menos enganosos ou simplificadores). A fragmentação da memória causa problemas de desempenho e uso eficiente da memória; portanto, às vezes é necessário considerá-la e tomar medidas para evitá-la. Um truque clássico é, se você alocar um monte de coisas que são do mesmo tamanho, pegando um pool de memória na inicialização que é algum múltiplo desse tamanho e gerenciando seu uso inteiramente internamente, garantindo assim que você não tenha fragmentação acontecendo no Nível do sistema operacional (e os buracos em seu mapeador de memória interna terão exatamente o tamanho certo para o próximo objeto daquele tipo que vier).
Existem bibliotecas inteiras de terceiros que não fazem nada além de lidar com esse tipo de coisa para você, e às vezes é a diferença entre um desempenho aceitável e algo que é executado muito lentamente.
malloc()
efree()
levam um tempo perceptível para serem executados, que você começará a notar se os ligar muito.Assim, evitando apenas ingenuamente usando
malloc()
efree()
você pode evitar ambos os problemas de fragmentação e de desempenho - mas quando você começa para baixo direito a ela, você deve sempre se certificar de quefree()
tudo o que vocêmalloc()
menos que você tenha uma boa razão para fazer o contrário. Mesmo ao usar um pool de memória interna, um bom aplicativo utilizará a memóriafree()
do pool antes de sair. Sim, o sistema operacional irá limpá-lo, mas se o ciclo de vida do aplicativo for alterado posteriormente, será fácil esquecer que o pool ainda está por aí ...É claro que os aplicativos de longa duração precisam ser extremamente escrupulosos quanto à limpeza ou reciclagem de tudo o que alocaram, ou acabam ficando sem memória.
fonte
Seus professores estão levantando um ponto importante. Infelizmente, o uso do inglês é tal que não tenho certeza do que eles dizem. Deixe-me responder à pergunta em termos de programas que não sejam de brinquedo, que têm certas características de uso de memória e com os quais trabalhei pessoalmente.
Alguns programas se comportam bem. Eles alocam memória em ondas: muitas alocações de pequeno ou médio porte seguidas por muitas liberações, em ciclos repetidos. Nestes programas, os alocadores de memória típicos funcionam muito bem. Eles aglutinam blocos liberados e, no final de uma onda, a maior parte da memória livre está em grandes blocos contíguos. Esses programas são bastante raros.
A maioria dos programas se comporta mal. Eles alocam e desalocam memória mais ou menos aleatoriamente, em uma variedade de tamanhos, de muito pequenos a muito grandes, e retêm um alto uso de blocos alocados. Nestes programas, a capacidade de coalescer blocos é limitada e, com o tempo, eles acabam com a memória altamente fragmentada e relativamente não contígua. Se o uso total da memória exceder cerca de 1,5 GB em um espaço de memória de 32 bits, e houver alocações de (digamos) 10 MB ou mais, eventualmente uma das grandes alocações falhará. Esses programas são comuns.
Outros programas liberam pouca ou nenhuma memória, até que parem. Eles alocam memória progressivamente durante a execução, liberando apenas pequenas quantidades, e então param, momento em que toda a memória é liberada. Um compilador é assim. Então é uma VM. Por exemplo, o tempo de execução .NET CLR, ele próprio escrito em C ++, provavelmente nunca libera memória. Por que deveria?
E essa é a resposta final. Nos casos em que o programa é suficientemente pesado no uso de memória, gerenciar a memória usando malloc e free não é uma resposta suficiente para o problema. A menos que você tenha sorte o suficiente para lidar com um programa bem comportado, você precisará projetar um ou mais alocadores de memória personalizados que pré-alocam grandes blocos de memória e então sub-alocam de acordo com uma estratégia de sua escolha. Você não pode usar o Grátis de forma alguma, exceto quando o programa for interrompido.
Sem saber exatamente o que seus professores disseram, para programas em escala de produção, provavelmente, eu ficaria do lado deles.
EDITAR
Terei uma chance de responder a algumas das críticas. Obviamente, o SO não é um bom lugar para postagens desse tipo. Só para ficar claro: tenho cerca de 30 anos de experiência escrevendo este tipo de software, incluindo alguns compiladores. Não tenho referências acadêmicas, apenas minhas próprias contusões. Não posso deixar de sentir que as críticas vêm de pessoas com experiências muito mais restritas e curtas.
Vou repetir minha mensagem principal: balancear malloc e free não é uma solução suficiente para alocação de memória em grande escala em programas reais. Bloco coalescente é normal, e ganha tempo, mas é não o suficiente. Você precisa de alocadores de memória sérios e inteligentes, que tendem a agarrar a memória em pedaços (usando malloc ou qualquer outro) e raramente liberam. Esta é provavelmente a mensagem que os professores de OP tinham em mente, que ele interpretou mal.
fonte
Estou surpreso que ninguém tenha citado O Livro ainda:
http://sarabander.github.io/sicp/html/5_002e3.xhtml#FOOT298
Portanto, de fato, muitos programas podem funcionar perfeitamente sem nunca se preocupar em liberar memória.
fonte
Eu sei de um caso em que liberar memória explicitamente é pior do que inútil . Ou seja, quando você precisa de todos os seus dados até o final da vida útil do processo. Em outras palavras, libertá-los só é possível antes do encerramento do programa. Visto que qualquer sistema operacional moderno se preocupa em liberar memória quando um programa morre, a chamada
free()
não é necessária nesse caso. Na verdade, pode retardar o encerramento do programa, uma vez que pode ser necessário acessar várias páginas na memória.fonte