Isso tem sido algo que me incomoda há muito tempo.
Todos nós somos ensinados na escola (pelo menos eu fui) que você DEVE libertar todos os indicadores que estão alocados. Estou um pouco curioso, porém, sobre o custo real de não liberar memória. Em alguns casos óbvios, como quando malloc
é chamado dentro de um loop ou parte de uma execução de encadeamento, é muito importante liberar para que não haja vazamentos de memória. Mas considere os dois exemplos a seguir:
Primeiro, se eu tiver código, é algo como isto:
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 se esgota, de modo que não há mal em perder a chamada 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 tenho um programa que funciona um pouco como um shell. Os usuários podem declarar variáveis como aaa = 123
e essas são armazenadas em alguma estrutura de dados dinâmica para uso posterior. Claramente, parece óbvio que você usaria alguma solução que chamaria alguma função * assign (hashmap, lista vinculada, algo assim). Para esse tipo de programa, não faz sentido nunca ser liberado após a chamada, malloc
porque essas variáveis devem estar presentes o tempo todo durante a execução do programa e não há uma boa maneira (que eu possa ver) de implementá-lo com espaço alocado estaticamente. É um projeto ruim ter um monte de memória alocada, mas liberada apenas como parte do processo que termina? Se sim, qual é a alternativa?
free(a)
realmente não faz nada para realmente liberar memória! Ele apenas redefine alguns indicadores na implementação libc do malloc, que monitora os pedaços de memória disponíveis dentro de uma grande página de memória mapeada (normalmente chamada de "heap"). Essa página ainda será liberada somente quando o programa terminar, não antes.Respostas:
Quase todos os sistemas operacionais modernos recuperam todo o espaço de memória alocado após a saída do programa. A única exceção em que posso pensar pode ser algo como o Palm OS, onde o armazenamento estático e a memória de tempo de execução do programa são praticamente a mesma coisa, portanto, não liberar pode fazer com que o programa ocupe mais armazenamento. (Só estou especulando aqui.)
Portanto, geralmente, não há mal nisso, exceto o custo de tempo de execução de ter mais armazenamento do que você precisa. Certamente, no exemplo que você dá, você deseja manter a memória de uma variável que pode ser usada até que seja limpa.
No entanto, é considerado bom estilo liberar memória assim que você não precisar mais dela e liberar qualquer coisa que você ainda tenha na saída do programa. É mais um exercício de saber qual memória você está usando e pensar se você ainda precisa dela. Se você não acompanhar, pode haver vazamento de memória.
Por outro lado, a advertência semelhante para fechar seus arquivos na saída tem um resultado muito mais concreto - se não o fizer, os dados que você escreveu para eles podem não ser liberados ou, se são um arquivo temporário, podem não ser seja excluído quando terminar. Além disso, os identificadores de banco de dados devem ter suas transações confirmadas e fechadas quando você terminar. Da mesma forma, se você estiver usando uma linguagem orientada a objetos como C ++ ou Objective C, não liberar um objeto quando terminar com isso significa que o destruidor nunca será chamado e quaisquer recursos responsáveis pela classe podem não ser limpos.
fonte
Sim, você está certo, seu exemplo não faz mal (pelo menos não na maioria dos sistemas operacionais modernos). Toda a memória alocada pelo seu processo será recuperada pelo sistema operacional assim que o processo terminar.
Fonte: Alocação e mitos do GC (alerta PostScript!)
Dito isto, você realmente deve tentar evitar todos os vazamentos de memória!
Segunda pergunta: seu design está bem. Se você precisar armazenar algo até o aplicativo sair, tudo bem fazer isso com a alocação dinâmica de memória. Se você não souber o tamanho necessário antecipadamente, não poderá usar a memória alocada estaticamente.
fonte
=== E quanto a provas futuras e reutilização de código ? ===
Se você não escrever o código para liberar os objetos, estará limitando o código a ser seguro apenas para uso quando puder depender da memória ser liberada pelo processo ser fechado ... ou seja, pequeno uso único projetos ou "descarte" [1] ) ... onde você sabe quando o processo terminará.
Se você faz escrever o código que free () é toda a sua memória alocada dinamicamente, então você está futura prova o código e deixar que os outros usá-lo em um projeto maior.
[1] no que diz respeito a projetos de "descarte". O código usado em projetos "Throw-away" tem uma maneira de não ser jogado fora. Em seguida, você sabe que dez anos se passaram e seu código de "descarte" ainda está sendo usado).
Eu ouvi uma história sobre um cara que escreveu um código apenas por diversão para fazer seu hardware funcionar melhor. Ele disse que " apenas um hobby, não será grande e profissional ". Anos depois, muitas pessoas estão usando seu código de "hobby".
fonte
malloc()
se for e terminar se o ponteiro ainda for nulo, essa função poderá ser usada com segurança um número arbitrário de vezes, mesmo quefree
nunca seja chamada. Eu acho que provavelmente vale a pena distinguir vazamentos de memória que podem consumir uma quantidade ilimitada de armazenamento, versus situações que só podem desperdiçar uma quantidade finita e previsível de armazenamento.null
se não houver uma alocação e não é nulo quando existe uma alocação, ter código livre da alocação e definir o ponteiro paranull
quando um contexto é destruído seria simples, especialmente se comparado a tudo o que seria necessário fazer para mover objetos estáticos para uma estrutura de contexto.Você está correto, nenhum dano é causado e é mais rápido sair
Há várias razões para isto:
Todos os ambientes de desktop e servidor simplesmente liberam todo o espaço de memória na saída (). Eles desconhecem estruturas de dados internas do programa, como pilhas.
Quase todas as
free()
implementações nunca retornam memória ao sistema operacional.Mais importante, é um desperdício de tempo quando é feito antes da saída (). Na saída, as páginas de memória e o espaço de troca são simplesmente liberados. Por outro lado, uma série de chamadas free () queima o tempo da CPU e pode resultar em operações de paginação de disco, falhas de cache e despejos de cache.
Com relação à possibilidade de reutilização futura de código, justificando a certeza de operações inúteis: isso é uma consideração, mas não é o caminho ágil . YAGNI!
fonte
Discordo completamente de todos que dizem que o OP está correto ou que não há danos.
Todo mundo está falando sobre um sistema operacional moderno e / ou legado.
Mas e se eu estiver em um ambiente em que simplesmente não possuo sistema operacional? Onde não há nada?
Imagine agora que você está usando interrupções com estilo de thread e aloque memória. No padrão C ISO / IEC: 9899, a vida útil da memória é declarada como:
Portanto, não é preciso dizer que o ambiente está fazendo o trabalho de liberação para você. Caso contrário, seria adicionado à última frase: "Ou até o programa terminar".
Portanto, em outras palavras: não liberar memória não é apenas uma má prática. Produz código não portátil e não compatível com C. O que pode pelo menos ser visto como 'correto, se o seguinte: [...] for suportado pelo ambiente'.
Mas nos casos em que você não possui sistema operacional, ninguém está fazendo o trabalho por você (eu sei que geralmente você não aloca e realoca a memória em sistemas incorporados, mas há casos em que você pode querer).
Portanto, falando em geral C simples (como o OP está marcado), isso está simplesmente produzindo códigos errados e não portáveis.
fonte
Normalmente libero todos os blocos alocados quando tenho certeza de que estou pronto. Hoje, o ponto de entrada do meu programa pode ser
main(int argc, char *argv[])
, mas amanhã pode serfoo_entry_point(char **args, struct foo *f)
e digitado como um ponteiro de função.Então, se isso acontecer, agora tenho um vazamento.
Em relação à sua segunda pergunta, se meu programa tivesse uma entrada como a = 5, eu alocaria espaço para a ou realocaria o mesmo espaço em um a = "foo" subsequente. Isso permaneceria alocado até:
Não consigo pensar em nenhum sistema operacional moderno que não recupere memória após a saída de um processo. Por outro lado, free () é barato, por que não limpar? Como já foi dito, ferramentas como o valgrind são ótimas para detectar vazamentos com os quais você realmente precisa se preocupar. Mesmo que os blocos que você exemplo sejam rotulados como 'ainda acessíveis', é apenas um ruído extra na saída quando você está tentando garantir que não haja vazamentos.
Outro mito é " Se está em main (), não preciso liberá-lo ", isso está incorreto. Considere o seguinte:
Se isso ocorreu antes da bifurcação / daemonização (e, teoricamente, para sempre), seu programa vazou um tamanho indeterminado de t 255 vezes.
Um programa bom e bem escrito deve sempre se limpar. Liberte toda a memória, limpe todos os arquivos, feche todos os descritores, desvincule todos os arquivos temporários, etc. detectar uma falha e retomar.
Realmente, seja gentil com a pobre alma que tem que manter suas coisas quando você passar para outras coisas.
fonte
free() is cheap
a menos que você tenha um bilhão de estruturas de dados com relacionamento complexo que precisa liberar uma a uma, percorrer a estrutura de dados para tentar liberar tudo pode acabar aumentando significativamente o tempo de desligamento, especialmente se metade dessa estrutura de dados já estiver paginada para o disco, sem nenhum benefício.Não há problema em deixar a memória livre quando você sai; malloc () aloca a memória da área de memória 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 em que é bom liberar tudo antes de sair é que os depuradores de memória (por exemplo, valgrind no Linux) detectam os blocos não liberados como vazamentos de memória e, se você também tiver vazamentos de memória "reais", isso se tornará mais difícil identificá-los se você também obtiver resultados "falsos" no final.
fonte
free
noexit
momento considerado prejudicial.Se você estiver usando a memória alocada, não estará fazendo nada de errado. Isso se torna um problema quando você escreve funções (que não sejam principais) que alocam memória sem liberá-la e sem disponibilizá-la para o restante do seu programa. Em seguida, seu programa continua sendo executado com a memória alocada, mas não há como usá-lo. Seu programa e outros programas em execução são privados dessa memória.
Editar: Não é 100% exato dizer que outros programas em execução estão privados dessa memória. O sistema operacional sempre pode permitir que eles o usem à custa da troca do seu programa para a memória virtual (
</handwaving>
). O ponto é, no entanto, que se o seu programa libera memória que não está usando, é menos provável que uma troca de memória virtual seja necessária.fonte
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 memória alocada; ele é executado de tal maneira que a memória é recuperada automaticamente. Parece tudo bem.
Depois, outra pessoa copia seu snippet no projeto dele, de forma que ele seja executado mil vezes por segundo. Essa pessoa agora tem um enorme vazamento de memória 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 cada departamento pode reutilizar o que a empresa possui. Portanto, 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.
fonte
Seu programa vazou a memória. Dependendo do seu sistema operacional, ele pode ter sido recuperado.
Mais moderno de desktop sistemas operacionais fazer recuperar a memória vazada no encerramento do processo, tornando-se, infelizmente, comum a ignorar o problema, como pode ser visto por muitas outras respostas aqui.)
Mas você está confiando em um recurso de segurança que você não deve confiar, e seu programa (ou função) pode ser executado em um sistema onde este comportamento faz resultado em um vazamento de memória "hard", na próxima vez.
Você pode estar executando no modo kernel ou em sistemas operacionais vintage / incorporados que não empregam proteção de memória como compensação. (As MMUs ocupam espaço no disco, a proteção de memória custa ciclos adicionais de CPU e não é pedir muito a um programador para limpar a si próprio).
Você pode usar e reutilizar a memória da maneira que desejar, mas aloque todos os recursos antes de sair.
fonte
Na verdade, há uma seção no livro on-line da OSTEP para um curso de graduação em sistemas operacionais que discute exatamente sua pergunta.
A seção relevante é "Esquecendo de liberar memória" no capítulo da API de memória na página 6, que fornece a seguinte explicação:
Este trecho está no contexto da introdução do conceito de memória virtual. Basicamente, neste ponto do livro, os autores explicam que um dos objetivos de um sistema operacional é "virtualizar memória", ou seja, permitir que todos os programas acreditem que têm acesso a um espaço de endereço de memória muito grande.
Nos bastidores, o sistema operacional traduz "endereços virtuais" que o usuário vê em endereços reais, apontando para a memória física.
No entanto, o compartilhamento de recursos, como memória física, requer que o sistema operacional acompanhe quais processos estão sendo usados. Portanto, se um processo termina, está dentro dos recursos e objetivos de design do sistema operacional recuperar a memória do processo, para que possa redistribuir e compartilhar a memória com outros processos.
EDITAR: O lado mencionado no trecho é copiado abaixo.
fonte
Não há perigo real em não liberar suas variáveis, mas se você atribuir um ponteiro a um bloco de memória para um bloco diferente, sem liberar o primeiro bloco, o primeiro bloco não estará mais acessível, mas ocupará espaço. Isso é chamado de vazamento de memória e, se você fizer isso com regularidade, seu processo começará a consumir mais e mais memória, retirando recursos do sistema de outros processos.
Se o processo for de curta duração, você poderá fazê-lo com frequência, pois toda a memória alocada é recuperada pelo sistema operacional quando o processo for concluído, mas eu recomendaria adquirir o hábito de liberar toda a memória para a qual você não tem mais uso.
fonte
Você está absolutamente correto a esse respeito. Em pequenos programas triviais em que uma variável deve existir até a morte do programa, não há benefício real em desalocar a memória.
De fato, eu já havia participado de um projeto em que cada execução do programa era muito complexa, mas relativamente curta, e a decisão era manter a memória 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 a falta de memória.
fonte
Você está correto, a memória é automaticamente liberada quando o processo termina. Algumas pessoas se esforçam para não fazer uma limpeza extensa quando o processo é finalizado, pois tudo será devolvido ao sistema operacional. No entanto, enquanto seu programa estiver em execução, você deverá liberar memória não utilizada. Caso contrário, você pode acabar se esgotando ou causar paginação excessiva se o conjunto de trabalho ficar muito grande.
fonte
Se você estiver desenvolvendo um aplicativo a partir do zero, poderá fazer algumas escolhas informadas sobre quando ligar gratuitamente. Seu programa de exemplo é bom: aloca memória, talvez você funcione por alguns segundos e depois fecha, liberando todos os recursos que reivindicou.
No entanto, se você estiver escrevendo algo mais - um servidor / aplicativo de execução demorada ou uma biblioteca para ser usada por outra pessoa, você deve esperar ligar gratuitamente para tudo o que fizer.
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ê faz mal. Se você não tem o hábito de observar vazamentos de memória sempre que codifica, poderá facilmente soltar alguns vazamentos. Então, em outras palavras, sim - você pode fugir sem ele; por favor, tenha cuidado, no entanto.
fonte
Se um programa esquecer de liberar alguns Megabytes antes de sair, o sistema operacional os libertará. Mas se o seu programa for executado por semanas seguidas e um loop dentro do programa esquecer de liberar alguns bytes em cada iteração, haverá um grande vazamento de memória que consumirá toda a memória disponível no seu computador, a menos que você o reinicie regularmente base => até pequenos vazamentos de memória podem ser ruins se o programa for usado para uma tarefa muito grande, mesmo que originalmente não tenha sido projetado para uma.
fonte
Penso que seus dois exemplos são, na verdade, apenas um: o
free()
deve ocorrer apenas no final do processo, o que, como você aponta, é inútil, pois o processo está terminando.No segundo exemplo, porém, a única diferença é que você permite um número indefinido de
malloc()
, o que pode levar à falta de memória. A única maneira de lidar com a situação é verificar o código de retornomalloc()
e agir em conformidade.fonte