Até onde podem ir os vazamentos de memória?

118

Já tive vazamentos de memória muitas vezes. Normalmente, quando estou mallocpensando que não há amanhã, ou pendurada FILE *como roupa suja. Eu geralmente suponho (leia: espero desesperadamente) que toda a memória é limpa pelo menos quando o programa termina. Existem situações em que a memória perdida não será coletada quando o programa for encerrado ou travado?

Se a resposta varia muito de uma linguagem para outra, vamos nos concentrar em C (++).

Observe o uso hiperbólico da frase 'como se não houvesse amanhã' e 'pendurado ... como roupa suja'. Inseguro * malloc* ing pode prejudicar quem você ama. Além disso, tenha cuidado com a roupa suja.

DilithiumMatrix
fonte
3
Se você estiver executando um sistema operacional "moderno" como Linux ou Windows, o próprio sistema operacional resolverá qualquer memória não liberada quando o programa for encerrado.
Oliver Charlesworth de
60
Em vez de fingir que não existe amanhã, tente fingir que existe um amanhã e mantenha o controle de sua memória!
William Pursell de
8
@WilliamPursell ah, então você está dizendo que callocgostaria que não houvesse amanhã. Excelente.
DilithiumMatrix
8
"Se a resposta variar muito de idioma para idioma, vamos nos concentrar em c (++)." c e c ++ não são a mesma língua!
Johnsyweb de
11
@zhermes: Comentar sobre C e C ++ serem linguagens diferentes esconde mais do que você pensa ... Em C ++ você prefere tirar vantagem de objetos com duração de armazenamento automática, siga o idioma RAII ... você deixa esses objetos cuidarem da memória gestão para você.
LihO

Respostas:

111

Não. Os sistemas operacionais liberam todos os recursos mantidos pelos processos quando eles saem.

Isso se aplica a todos os recursos que o sistema operacional mantém: memória, arquivos abertos, conexões de rede, identificadores de janela ...

Dito isso, se o programa estiver sendo executado em um sistema embarcado sem sistema operacional ou com um sistema operacional muito simples ou cheio de erros, a memória pode ficar inutilizável até a reinicialização. Mas se você estivesse nessa situação, provavelmente não estaria fazendo esta pergunta.

O sistema operacional pode levar muito tempo para liberar certos recursos. Por exemplo, a porta TCP que um servidor de rede usa para aceitar conexões pode levar alguns minutos para ficar livre, mesmo se fechada corretamente pelo programa. Um programa em rede também pode conter recursos remotos , como objetos de banco de dados. O sistema remoto deve liberar esses recursos quando a conexão de rede for perdida, mas pode demorar ainda mais do que o sistema operacional local.

Joni
fonte
5
Um paradigma comum em RTOSs é o modelo de processo único, thread múltiplo e nenhuma proteção de memória entre 'tarefas'. Geralmente há uma pilha. Certamente é assim que VxWorks costumava funcionar - e provavelmente ainda funciona.
Marko
29
Observe que nem todos os recursos podem ser liberados pelo sistema operacional. Conexões de rede, transações de banco de dados, etc, não fechá-los explicitamente pode causar alguns resultados indesejáveis. O não fechamento da conexão de rede pode fazer com que o servidor pense que você ainda está ativo por um período indefinido e, para servidores que limitam o número de conexões ativas, pode acidentalmente causar negação de serviço. Não fechar as transações do banco de dados pode causar a perda de dados não confirmados.
Lie Ryan
1
@Marko: A versão recente do vxWorks agora oferece suporte a RTPs (processos em tempo real) que oferecem suporte à proteção de memória.
Xavier T.
20
"Os sistemas operacionais liberam todos os recursos mantidos pelos processos quando eles saem." Não é estritamente verdade. Por exemplo, no (pelo menos) Linux, semáforos SysV e outros objetos IPC não são limpos na saída do processo. É por isso que existe a ipcrmlimpeza manual, linux.die.net/man/8/ipcrm .
sleske
7
Além disso, se um objeto tiver um arquivo temporário que ele mantém, isso claramente não será limpo depois.
Mooing Duck
47

O padrão C não especifica que a memória alocada por mallocseja liberada quando o programa termina. Isso feito pelo sistema operacional e nem todos os sistemas operacionais (geralmente estes estão no mundo integrado) liberam a memória quando o programa termina.

ouah
fonte
20
Isso é mais ou menos porque o padrão C fala sobre programas C, não os sistemas operacionais nos quais C acontece de ser executado ...
vonbrand
5
@vonbrand O C Standard poderia ter um parágrafo que dizia que quando mainretorna toda a memória alocada pelo mallocé liberada. Por exemplo, ele diz que todos os arquivos abertos são fechados antes do encerramento do programa. Para memória alocada malloc, simplesmente não é especificado. É claro que minha frase sobre o sistema operacional descreve o que normalmente é feito, não o que o padrão prescreve, pois não especifica nada sobre isso.
ouah
Deixe-me corrigir meu comentário: o padrão fala sobre C, não sobre como o programa é iniciado e interrompido. Você pode muito bem escrever um programa C que seja executado sem um sistema operacional. Nesse caso, não há ninguém que fará a limpeza. O padrão muito deliberadamente não especificar nada, excepto se necessário, de modo a usos não restringem sem necessidade.
vonbrand
2
@ouah: " quando o principal retornar ...". Isso é uma suposição. Temos que considerar " se o principal retornar ...". std::atexittambém considera o encerramento do programa por meio de std::exit, e há também std::aborte (específico do C ++) std::terminate.
MSalters
@ouah: Se isso tivesse sido incluído, atexitnão seria utilizável. :-)
R .. GitHub PARAR DE AJUDAR O ICE
28

Como todas as respostas cobriram a maioria dos aspectos de sua pergunta sobre sistemas operacionais modernos, mas historicamente, há um que vale a pena mencionar se você já programou no mundo DOS. Os programas Terminant and Stay Resident (TSR) normalmente devolveriam o controle ao sistema, mas residiriam na memória que poderia ser revivida por uma interrupção de software / hardware. Era normal ver mensagens como "sem memória! Tente descarregar alguns de seus TSRs" ao trabalhar nesses sistemas operacionais.

Então, tecnicamente, o programa termina , mas como ele ainda reside na memória, qualquer vazamento de memória não seria liberado a menos que você descarregue o programa.

Portanto, você pode considerar que este é outro caso além dos sistemas operacionais que não estão reivindicando memória, seja porque há bugs ou porque o sistema operacional integrado foi projetado para isso.

Lembro-me de mais um exemplo. O Customer Information Control System (CICS), um servidor de transações que é executado principalmente em mainframes IBM, é pseudo-conversacional. Quando executado, ele processa os dados inseridos pelo usuário, gera outro conjunto de dados para o usuário, transfere para o nó terminal do usuário e finaliza. Ao ativar a chave de atenção, ela se reativa novamente para processar outro conjunto de dados. Devido à maneira como ele se comporta, tecnicamente novamente, o SO não recuperará memória dos Programas CICS encerrados, a menos que você recicle o servidor de transações CICS.

Abhijit
fonte
Isso é muito interessante, obrigado pela nota histórica! Você sabe se esse paradigma se deve ao fato de que a liberação de memória é muito cara em termos computacionais se não for necessário? Ou a alternativa simplesmente nunca foi pensada?
DilithiumMatrix
1
@zhermes: Era computacionalmente impossível, já que o DOS simplesmente não rastreava alocações de memória para TSRs. Quase por definição: o objetivo era permanecer residente . Se você deseja que seu TSR libere parte, mas não toda a memória, cabe a você decidir o que liberar.
MSalters
2
@zhermes: DOS (como o CP / M, seu ancestral) não era o que você chamaria de sistema operacional no sentido moderno. Na verdade, era apenas uma coleção de utilitários de E / S que podiam ser chamados de uma forma padrão agrupada com um processador de comando que permitia a execução de um programa por vez. Não havia noção de processos e a memória não era virtual nem protegida. Os TSRs eram um hack útil que podia dizer ao sistema que eles estavam ocupando até 64 K de espaço e que se conectavam a interrupções para serem chamados.
Blrfl
8

Como os outros já disseram, a maioria dos sistemas operacionais irá recuperar a memória alocada após o término do processo (e provavelmente outros recursos como soquetes de rede, identificadores de arquivo, etc).

Dito isso, a memória pode não ser a única coisa com a qual você precisa se preocupar ao lidar com new / delete (ao invés de raw malloc / free). A memória alocada em novo pode ser recuperada, mas as coisas que podem ser feitas nos destruidores dos objetos não acontecerão. Talvez o destruidor de alguma classe grave um valor sentinela em um arquivo após a destruição. Se o processo simplesmente terminar, o identificador de arquivo pode ser liberado e a memória recuperada, mas esse valor sentinela não seria gravado.

Moral da história, sempre limpe depois de você mesmo. Não deixe as coisas balançarem. Não confie na limpeza do sistema operacional depois de você. Limpe depois de você mesmo.

Andre Kostur
fonte
'Não confie na limpeza do sistema operacional depois de você. Limpe depois de você mesmo. ' Isso geralmente é imp ... 'muito, muito difícil' com aplicativos multithread complexos. Vazamentos reais, onde todas as referências a um recurso foram perdidas, são ruins. Permitir que o sistema operacional faça uma limpeza em vez de liberar referências explicitamente nem sempre é ruim e, frequentemente, o único caminho razoável a seguir.
Martin James
1
Em C ++, os destruidores serão chamados ao finalizar o programa (a menos que algum kill -9ventilador menos que brilhante apareça ...)
vonbrand
@vonbrand Verdadeiro, mas se estivermos falando de vazamentos com objetos dinâmicos, esses destruidores não ocorrerão. O objeto que sai do escopo é um ponteiro bruto e seu destruidor é autônomo. (Claro, veja os objetos RAII para atenuar esse problema ...)
Andre Kostur
1
O problema com o RAII é que ele insiste em desalocar objetos na saída do processo dos quais não é realmente importante se livrar. Você quer ter cuidado com as conexões de banco de dados, mas a memória geral é melhor limpa pelo sistema operacional (faz um trabalho muito melhor). O problema se manifesta como um programa que leva uma eternidade para ser encerrado quando a quantidade de memória paginada para fora aumenta. Também não é trivial resolver ...
Donal Fellows
@vonbrand: Não é tão simples. std::exitchamará dtors, std::abortnão, talvez exceções não detectadas.
MSalters
7

É mais provável que isso dependa do sistema operacional do que do idioma. Em última análise, qualquer programa em qualquer idioma obterá sua memória do sistema operacional.

Nunca ouvi falar de um sistema operacional que não recicla a memória quando um programa é encerrado / travado. Portanto, se o seu programa tem um limite superior na memória que precisa alocar, então apenas alocar e nunca liberar é perfeitamente razoável.

John
fonte
Você poderia estragar a imagem da memória do kernel no caso de um sistema operacional simplista? .. Tipo, aqueles sistemas operacionais sem mesmo multitarefa.
ulidtko
@ulidtko, isso vai bagunçar as coisas. Se meu programa requer, digamos, 1GiB de vez em quando, e agarra isso enquanto durar, ele está negando o uso desses recursos a outros, mesmo quando não os estiver usando. Isso pode importar hoje, ou não. Mas o ambiente vai mudar radicalmente. Garantido.
vonbrand
@vonbrand O uso raro de 1GiB normalmente não é um problema (contanto que você tenha muita memória física), pois os sistemas operacionais modernos podem localizar os bits que não estão ativos no momento. O problema surge quando você tem mais memória virtual no ativo uso do que você tem memória física em que para hospedá-lo.
Donal Fellows
5

Se o programa for transformado em um componente dinâmico ("plugin") que é carregado no espaço de endereço de outro programa, será problemático, mesmo em um sistema operacional com gerenciamento de memória organizado. Não precisamos nem pensar no código sendo portado para sistemas menos capazes.

Por outro lado, a liberação de toda a memória pode afetar o desempenho da limpeza de um programa.

Em um programa em que eu estava trabalhando, um determinado caso de teste exigia 30 segundos ou mais para o programa sair, porque estava recorrendo ao gráfico de toda a memória dinâmica e liberando-o peça por peça.

Uma solução razoável é ter o recurso lá e cobri-lo com casos de teste, mas desligá-lo no código de produção para que o aplicativo feche rapidamente.

Kaz
fonte
5

Todos os sistemas operacionais que merecem o título irão limpar a bagunça que seu processo fez após o término. Mas sempre há imprevistos, e se fosse negado o acesso de alguma forma e algum pobre programador não previsse a possibilidade e então não tente novamente um pouco mais tarde? É sempre mais seguro apenas se limpar SE os vazamentos de memória forem essenciais - caso contrário, não vale o esforço IMO se esse esforço for caro.

Edit: Você precisa limpar vazamentos de memória se eles estiverem no lugar onde eles se acumulam, como em loops. Os vazamentos de memória de que falo são aqueles que se acumulam em tempo constante durante o curso do programa; se você tiver um vazamento de qualquer outro tipo, provavelmente será um problema sério mais cedo ou mais tarde.

Em termos técnicos, se seus vazamentos são de 'complexidade' de memória O (1), eles são bons na maioria dos casos, O (logn) já desagradável (e em alguns casos fatais) e O (N) + intolerável.


fonte
3

A memória compartilhada em sistemas compatíveis com POSIX persiste até que shm_unlink seja chamado ou o sistema seja reinicializado.

klearn
fonte
2

Se você tiver comunicação entre processos, isso pode fazer com que outros processos nunca concluam e consumam recursos, dependendo do protocolo.

Para dar um exemplo, uma vez eu estava experimentando imprimir em uma impressora PDF em Java quando encerrei o JVM no meio de um trabalho de impressora, o processo de spool de PDF permaneceu ativo e tive que encerrá-lo no gerenciador de tarefas antes de poder tente imprimir novamente.

aberração da catraca
fonte