É sempre OK * não * usar free () na memória alocada?

83

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?

usuario
fonte
11
Eles não estão "errados" - eles têm um ponto válido (embora limitado) sobre a fragmentação de regiões livres muito pequenas e isoladas e provavelmente o declararam com um pouco mais de cuidado do que você relatou.
Chris Stratton
2
As únicas ocasiões em que você não precisa liberar memória é ao usar a memória gerenciada ou quando vai reutilizar a memória original alocada. Suspeito que o motivo pelo qual dois instrutores disseram isso é porque você está falando sobre um programa específico que pode reutilizar a memória. Nesse caso, você ainda usaria free (), mas apenas no final de seu aplicativo. Tem certeza de que não era esse o significado?
krowe
17
@Marian: Tive um professor que afirmou que em C e C ++ é necessário liberar sua memória alocada em uma função definida no mesmo arquivo .c / .cxx em que foi alocado ... Essas pessoas às vezes parecem sofrer gravemente de hipóxia devido a morar muito alto em uma torre de marfim.
PlasmaHH
4
Existem alguns programas que não são de brinquedo que não desalocam a memória, e deixar o sistema operacional limpar tudo na saída do processo é muito mais rápido do que (confusamente) manter muitos registros contábeis para que você possa fazer isso sozinho.
Donal Fellows de
9
Nunca mescle coisas que você ouviu em seu cérebro sem questionar. Tive muitos professores, leitores e corretores que estavam errados ou desatualizados. E sempre analise o que eles dizem com muita precisão. Nosso povo costuma ser bastante preciso e pode dizer coisas que são corretas, mas são fáceis de entender de maneira errada ou com a prioridade errada por alguém que se sente em casa apenas na linguagem comum. Por exemplo, lembro-me que na escola um professor disse "Você fez sua lição de casa.", Eu disse "Não". Enquanto eu estava correto, o professor achou isso ofensivo, porque eu economizei tempo para encontrar desculpas esfarrapadas, o que ele não esperava.
Sebastian Mach

Respostas:

100

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:

  • Tente ler o código para dlmalloc . É muito mais avançado, sendo uma implementação de qualidade de produção total.
  • Mesmo em aplicativos incorporados, implementações de fragmentação estão disponíveis. Veja, por exemplo, essas notas sobre o heap4.ccódigo em FreeRTOS .
desanuviar
fonte
1
Você pode me fornecer algumas referências?
Nick de
4
Acho que vale a pena mencionar que o espaço de endereço virtual não é uma representação direta da memória física. E essa fragmentação na memória física pode ser tratada pelo SO, enquanto a memória virtual não liberada pelo processo também não será liberada fisicamente.
lapk de
@PetrBudnik seria raro que a memória virtual mapeie 1-1 para a memória física de qualquer maneira, o sistema operacional pensará em mapeamentos de página e será capaz de trocá-lo dentro e fora com o mínimo de barulho
loucura por catraca
3
Comentário muito minucioso: Embora o conceito seja bem explicado, o exemplo real foi escolhido um pouco ... azar. Para qualquer um olhando o código-fonte de digamos dlmalloc e ficando confuso: Blocos abaixo de um tamanho específico são sempre potências de 2 e são mesclados / divididos de acordo. Portanto, acabaríamos (possivelmente) com um bloco de 8 bytes e 1 bloco de 4 bytes, mas nenhum bloco de 12 bytes. Essa é uma abordagem bastante padrão para alocadores, pelo menos em desktops, embora talvez os aplicativos incorporados tentem ser mais cuidadosos com sua sobrecarga.
Voo
@Voo Eu removi a menção de um tamanho para os blocos no exemplo, não importava de qualquer maneira. Melhor?
relaxe
42

As outras respostas já explicam perfeitamente bem que as implementações reais de malloc()e free()realmente aglutinam (desfragmentam) buracos em pedaços livres maiores. Mas mesmo que não fosse o caso, ainda seria uma má ideia renunciar free().

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.

Angew não está mais orgulhoso de SO
fonte
6
+1 Exatamente. O fato é que, se freeestá 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, freemas onde mallocseja 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.
Jason C
10

É 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 .

Paul Evans
fonte
9

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).

Luaan
fonte
Deixar o sistema operacional liberar tudo no final não é apenas uma questão de desempenho - também precisa de muito menos lógica para funcionar.
hugomg
@missingno Em outras palavras, permite que você saiba como o gerenciamento de memória difícil pode ser :) No entanto, eu veria isso como um argumento contra linguagens não gerenciadas - se o seu motivo é complicado liberar lógica em vez de desempenho, talvez seja melhor usando uma linguagem / ambiente que cuida disso para você.
Luaan
5

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.

Paddy3118
fonte
10
Este é um bom ponto. No entanto, eu esperaria que eles não usassem mallocnada 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).
Luaan
2

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, newe 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 mallocs não forem frequentes, essa é uma possibilidade real.

Caramba, se você sabe que está indo para mallocalgo apenas uma vez, e que mallocnunca 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 de malloc32 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çamallocessas 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: mallocdeve sempre ser associado a a free. newdeve sempre ter um delete.

Shaz
fonte
2
Na maioria dos sistemas embarcados e em tempo real, uma bomba-relógio que causasse falha após apenas 73 horas seria um problema grave.
Ben Voigt
inteiros típicos ??? O número inteiro é pelo menos um número de 16 bits e em microchips pequenos geralmente é de 16 bits. Geralmente, há mais dispositivos com sizeof(int)igual a 2 em vez de 4.
ST3
2

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.

mfro
fonte
2

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()e free()levam um tempo perceptível para serem executados, que você começará a notar se os ligar muito.

Assim, evitando apenas ingenuamente usando malloc()e free()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 que free()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ória free()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.

Matthew Walton
fonte
1

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.

david.pfx
fonte
1
Os programas de mau comportamento mostram que os alocadores decentes devem usar pools separados para blocos de tamanhos diferentes. Com a memória virtual, ter muitos pools distintos não deve ser um grande problema. Se cada bloco for arredondado para uma potência de dois, e cada pool contiver, portanto, blocos arredondados de apenas um tamanho, não consigo ver como a fragmentação poderia ficar muito ruim. Na pior das hipóteses, um programa poderia parar repentinamente de se interessar por uma determinada faixa de tamanho, deixando algumas piscinas praticamente vazias sem uso; Eu não acho que esse seja um comportamento muito comum.
Marc van Leeuwen
5
Citação muito necessária para a afirmação de que programas escritos com competência não liberam memória até que o aplicativo feche.
12
Toda essa resposta parece uma série de suposições e suposições aleatórias. Existe alguma coisa para fazer backup disso?
Chris Hayes
4
Não tenho certeza do que você quer dizer com o tempo de execução do .NET CLR que não libera memória. Tanto quanto eu testei, se puder.
Theodoros Chatzigiannakis
1
@vonbrand: GCC tem vários alocadores, incluindo sua própria marca de coletor de lixo. Ele consome memória durante as passagens e a coleta entre as passagens. A maioria dos compiladores para outras linguagens tem no máximo 2 passagens e libera pouca ou nenhuma memória entre as passagens. Se você discordar, fico feliz em pesquisar qualquer exemplo que você der.
david.pfx
1

Estou surpreso que ninguém tenha citado O Livro ainda:

Isso pode não ser verdade eventualmente, porque as memórias podem ficar grandes o suficiente para que seja impossível ficar sem memória livre durante a vida útil do computador. Por exemplo, existem cerca de 3 ⋅ 10 13 microssegundos em um ano, então, se tivéssemos uma redução por microssegundo, precisaríamos de cerca de 10 15 células de memória para construir uma máquina que pudesse operar por 30 anos sem ficar sem memória. Essa quantidade de memória parece absurdamente grande para os padrões de hoje, mas não é fisicamente impossível. Por outro lado, os processadores estão ficando mais rápidos e um futuro computador pode ter um grande número de processadores operando em paralelo em uma única memória, portanto, pode ser possível usar a memória muito mais rápido do que postulamos.

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.

Oakad
fonte
1

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.

Miklós Homolya
fonte