Sempre ouvi dizer que em C você realmente precisa observar como administra a memória. E ainda estou começando a aprender C, mas até agora não tive que fazer nenhuma atividade relacionada ao gerenciamento de memória. Sempre imaginei ter que liberar variáveis e fazer todo tipo de coisa feia. Mas não parece ser o caso.
Alguém pode me mostrar (com exemplos de código) um exemplo de quando você teria que fazer algum "gerenciamento de memória"?
Respostas:
Existem dois locais onde as variáveis podem ser colocadas na memória. Quando você cria uma variável como esta:
As variáveis são criadas na " pilha ". As variáveis de pilha são liberadas automaticamente quando saem do escopo (ou seja, quando o código não pode mais alcançá-las). Você pode ouvi-las chamadas de variáveis "automáticas", mas isso saiu de moda.
Muitos exemplos para iniciantes usarão apenas variáveis de pilha.
A pilha é boa porque é automática, mas também tem duas desvantagens: (1) O compilador precisa saber com antecedência o tamanho das variáveis e (b) o espaço da pilha é um tanto limitado. Por exemplo: no Windows, nas configurações padrão do vinculador da Microsoft, a pilha é definida como 1 MB e nem toda ela está disponível para suas variáveis.
Se você não sabe em tempo de compilação o quão grande é o seu array, ou se você precisa de um grande array ou struct, você precisa do "plano B".
O plano B é chamado de " heap ". Normalmente, você pode criar variáveis tão grandes quanto o sistema operacional permitir, mas você tem que fazer isso sozinho. Postagens anteriores mostraram uma maneira de fazer isso, embora existam outras maneiras:
(Observe que as variáveis na pilha não são manipuladas diretamente, mas por meio de ponteiros)
Depois de criar uma variável de heap, o problema é que o compilador não pode dizer quando você terminou com ela, então você perde a liberação automática. É aí que entra a "liberação manual" a que você se referia. Seu código agora é responsável por decidir quando a variável não é mais necessária e liberá-la para que a memória possa ser usada para outros fins. Para o caso acima, com:
O que torna essa segunda opção um "negócio desagradável" é que nem sempre é fácil saber quando a variável não é mais necessária. Esquecer de liberar uma variável quando você não precisa dela fará com que seu programa consuma mais memória do que precisa. Essa situação é chamada de "vazamento". A memória "vazada" não pode ser usada para nada até que o programa termine e o sistema operacional recupere todos os seus recursos. Problemas ainda mais desagradáveis são possíveis se você liberar uma variável de heap por engano antes de realmente terminar com ela.
Em C e C ++, você é responsável por limpar suas variáveis de heap como mostrado acima. No entanto, existem linguagens e ambientes como Java e linguagens .NET como C # que usam uma abordagem diferente, onde o heap é limpo por conta própria. Este segundo método, chamado de "coleta de lixo", é muito mais fácil para o desenvolvedor, mas você paga uma penalidade na sobrecarga e no desempenho. É um equilíbrio.
(Eu encostei muitos detalhes para dar uma resposta mais simples, mas espero que mais nivelada)
fonte
malloc()
sua causa UB,(char *)malloc(size);
consulte stackoverflow.com/questions/605845/…Aqui está um exemplo. Suponha que você tenha uma função strdup () que duplica uma string:
E você chama assim:
Você pode ver que o programa funciona, mas alocou memória (via malloc) sem liberá-la. Você perdeu o ponteiro para o primeiro bloco de memória ao chamar strdup pela segunda vez.
Isso não é grande coisa para essa pequena quantidade de memória, mas considere o caso:
Você acabou de usar 11 GB de memória (possivelmente mais, dependendo do seu gerenciador de memória) e, se não travou, o processo provavelmente está lento.
Para corrigir, você precisa chamar free () para tudo o que é obtido com malloc () depois de terminar de usá-lo:
Espero que este exemplo ajude!
fonte
Você precisa fazer "gerenciamento de memória" quando quiser usar a memória no heap em vez da pilha. Se você não souber o tamanho de um array até o tempo de execução, terá que usar o heap. Por exemplo, você pode querer armazenar algo em uma string, mas não sabe quão grande será seu conteúdo até que o programa seja executado. Nesse caso, você escreveria algo assim:
fonte
Acho que a maneira mais concisa de responder à pergunta é considerar o papel do ponteiro em C. O ponteiro é um mecanismo leve, mas poderoso, que lhe dá imensa liberdade ao custo de uma imensa capacidade de atirar no próprio pé.
Em C, a responsabilidade de garantir que seus indicadores apontem para a memória que você possui é sua e somente sua. Isso requer uma abordagem organizada e disciplinada, a menos que você ignore as dicas, o que torna difícil escrever um C.
As respostas postadas até agora se concentram em alocações de variáveis automáticas (pilha) e heap. Usar a alocação de pilha cria uma memória conveniente e gerenciada automaticamente, mas em algumas circunstâncias (grandes buffers, algoritmos recursivos) pode levar ao terrível problema de estouro de pilha. Saber exatamente quanta memória você pode alocar na pilha depende muito do sistema. Em alguns cenários integrados, algumas dezenas de bytes podem ser o seu limite; em alguns cenários de desktop, você pode usar megabytes com segurança.
A alocação de heap é menos inerente ao idioma. É basicamente um conjunto de chamadas de biblioteca que garante a propriedade de um bloco de memória de determinado tamanho até que você esteja pronto para devolvê-lo ('liberá-lo'). Parece simples, mas está associado a uma dor incalculável do programador. Os problemas são simples (liberar a mesma memória duas vezes ou não [vazamentos de memória], não alocar memória suficiente [estouro de buffer], etc), mas difíceis de evitar e depurar. Uma abordagem altamente disciplinada é absolutamente obrigatória na prática, mas é claro que a linguagem não a exige.
Eu gostaria de mencionar outro tipo de alocação de memória que foi ignorado por outros posts. É possível alocar variáveis estaticamente, declarando-as fora de qualquer função. Acho que, em geral, esse tipo de alocação tem uma má reputação porque é usado por variáveis globais. No entanto, não há nada que diga que a única maneira de usar a memória alocada dessa forma é como uma variável global indisciplinada em uma bagunça de código espaguete. O método de alocação estática pode ser usado simplesmente para evitar algumas das armadilhas do heap e métodos de alocação automática. Alguns programadores C ficam surpresos ao saber que grandes e sofisticados programas de jogos e embarcados em C foram construídos sem o uso de alocação de heap.
fonte
Existem algumas ótimas respostas aqui sobre como alocar e liberar memória e, na minha opinião, o lado mais desafiador de usar C é garantir que a única memória que você usa seja a que você alocou - se isso não for feito corretamente, o que você termina é o primo deste site - um estouro de buffer - e você pode estar sobrescrevendo a memória que está sendo usada por outro aplicativo, com resultados muito imprevisíveis.
Um exemplo:
Neste ponto, você alocou 5 bytes para myString e o preencheu com "abcd \ 0" (strings terminam em nulo - \ 0). Se sua alocação de string foi
Você estaria atribuindo "abcde" nos 5 bytes alocados para o seu programa, e o caractere nulo final seria colocado no final disso - uma parte da memória que não foi alocada para seu uso e pode ser grátis, mas também pode estar sendo usado por outro aplicativo - Esta é a parte crítica do gerenciamento de memória, onde um erro terá consequências imprevisíveis (e às vezes irrepetíveis).
fonte
strcpy()
vez de=
; Presumo que seja essa a intenção de Chris BC.Uma coisa a lembrar é sempre inicializar seus ponteiros para NULL, já que um ponteiro não inicializado pode conter um endereço de memória válido pseudo-aleatório que pode fazer com que os erros de ponteiro ocorram silenciosamente. Ao forçar um ponteiro a ser inicializado com NULL, você sempre pode detectar se está usando esse ponteiro sem inicializá-lo. O motivo é que os sistemas operacionais "conectam" o endereço virtual 0x00000000 a exceções de proteção geral para interceptar o uso de ponteiro nulo.
fonte
Além disso, você pode querer usar a alocação de memória dinâmica quando precisar definir uma grande matriz, digamos int [10000]. Você não pode simplesmente colocá-lo na pilha porque então, hm ... você terá um estouro de pilha.
Outro bom exemplo seria a implementação de uma estrutura de dados, digamos, lista vinculada ou árvore binária. Não tenho um código de exemplo para colar aqui, mas você pode pesquisar no Google facilmente.
fonte
(Estou escrevendo porque sinto que as respostas até agora não são muito corretas.)
O motivo pelo qual vale a pena mencionar o gerenciamento de memória é quando você tem um problema / solução que exige a criação de estruturas complexas. (Se seus programas travarem se você alocar muito espaço na pilha de uma vez, isso é um bug.) Normalmente, a primeira estrutura de dados que você precisa aprender é algum tipo de lista . Aqui está um único link, no topo da minha cabeça:
Naturalmente, você gostaria de algumas outras funções, mas basicamente, é para isso que você precisa do gerenciamento de memória. Devo salientar que existem vários truques que são possíveis com o gerenciamento de memória "manual", por exemplo,
Obtenha um bom depurador ... Boa sorte!
fonte
@ Euro Micelli
Um ponto negativo a adicionar é que os ponteiros para a pilha não são mais válidos quando a função retorna, portanto, você não pode retornar um ponteiro para uma variável da pilha de uma função. Este é um erro comum e um dos principais motivos pelos quais você não consegue sobreviver apenas com variáveis de pilha. Se sua função precisar retornar um ponteiro, você terá que malloc e lidar com o gerenciamento de memória.
fonte
Você está correto, é claro. Acredito que sempre foi verdade, embora eu não tenha uma cópia de K&R para verificar.
Eu não gosto muito das conversões implícitas em C, então eu tendo a usar conversões para tornar a "mágica" mais visível. Às vezes ajuda a legibilidade, às vezes não e às vezes faz com que um bug silencioso seja detectado pelo compilador. Ainda assim, não tenho uma opinião forte sobre isso, de uma forma ou de outra.
Sim ... você me pegou lá. Eu passo muito mais tempo em C ++ do que em C. Obrigado por notar isso.
fonte
Em C, você realmente tem duas opções diferentes. Um, você pode deixar o sistema gerenciar a memória para você. Alternativamente, você pode fazer isso sozinho. Geralmente, você deseja manter o primeiro tanto quanto possível. No entanto, a memória gerenciada automaticamente em C é extremamente limitada e você precisará gerenciar manualmente a memória em muitos casos, como:
uma. Você quer que a variável sobreviva às funções e não quer ter uma variável global. ex:
b. você deseja ter memória alocada dinamicamente. O exemplo mais comum é a matriz sem comprimento fixo:
Veja, um valor longo é suficiente para conter QUALQUER COISA. Apenas lembre-se de liberá-lo ou você se arrependerá. Este é um dos meus truques favoritos para se divertir em C: D.
No entanto, geralmente, você gostaria de ficar longe de seus truques favoritos (T___T). Você IRÁ quebrar seu sistema operacional, mais cedo ou mais tarde, se usá-los com muita frequência. Contanto que você não use * alloc e free, é seguro dizer que você ainda é virgem e que o código ainda parece bom.
fonte
Certo. Se você criar um objeto que existe fora do escopo em que você o usa. Aqui está um exemplo inventado (tenha em mente que minha sintaxe estará errada; meu C está enferrujado, mas este exemplo ainda ilustrará o conceito):
Neste exemplo, estou usando um objeto do tipo SomeOtherClass durante o tempo de vida de MyClass. O objeto SomeOtherClass é usado em várias funções, portanto, aloquei dinamicamente a memória: o objeto SomeOtherClass é criado quando MyClass é criado, usado várias vezes durante a vida útil do objeto e, em seguida, liberado quando MyClass é liberado.
Obviamente, se fosse um código real, não haveria razão (além do possível consumo de memória da pilha) para criar myObject dessa forma, mas esse tipo de criação / destruição de objeto torna-se útil quando você tem muitos objetos e deseja controlar com precisão quando eles são criados e destruídos (para que seu aplicativo não absorva 1 GB de RAM por toda a sua vida, por exemplo), e em um ambiente em janela, isso é praticamente obrigatório, como objetos que você cria (botões, por exemplo) , precisam existir bem fora do escopo de qualquer função específica (ou mesmo da classe).
fonte