Alocar memória dinamicamente é como os programas C aumentam e diminuem enquanto são executados. Você não conhece as necessidades exatas do seu aplicativo até que ele esteja realmente em execução. A solução é a pilha. Você solicita um bloqueio. O sistema operacional o reserva. Você usa isso. Então você devolve com free. Este ciclo permite que outros programas reciclem a mesma RAM.
Considere este exemplo simples. É a interação mais básica que você pode obter com o heap.
Como Malloc realmente funciona
A função malloc não é mágica. É um mecânico verificando o inventário. Quando você liga, três coisas acontecem em sequência.
- Verifica a capacidade atual do heap. Ele analisa o parâmetro que você passou – neste caso
sizeof(int), que tem 4 bytes. Ele pergunta se existe espaço contíguo suficiente. Se o heap estiver fragmentado ou cheio, ele falhará. - Se existir espaço, ele reserva esse bloco. O sistema o marca como “seu”. Nenhuma outra chamada
mallocpode tocar neste pedaço. Evita colisões acidentais entre alocações. - Retorna o endereço desse bloco. Ele não fornece os dados em si. Fornece uma variável de ponteiro. Essa variável contém a localização. Agora você tem um identificador para um local específico na RAM.
Em nosso exemplo, p recebe esse endereço. A variável ponteiro é apenas um contêiner para um local de memória. O valor inteiro real reside nesse local.
Por que você deve verificar NULL
Você vê a verificação if (p == 0) imediatamente após a alocação. Alguns iniciantes ignoram isso. É um erro fatal.
Sempre verifique o ponteiro após qualquer chamada ao malloc para garantir que o ponteiro é válido.
O tamanho do heap flutua constantemente. Depende de quais outros processos estão em execução. Depende de quanta memória eles capturaram. Não há garantia de que uma chamada será bem-sucedida. Se malloc falhar, ele retornará zero. Zero é NULO. Se você tentar desreferenciar um ponteiro nulo, seu programa travará. Duro.
Você poderia escrever if (p == NULL) ou if (!p). Eles significam a mesma coisa. Mas você deve verificar. Toda vez.
O que acontece se você esquecer de liberar memória?
Se o seu programa terminar e você não chamar free, o sistema operacional será limpo. Ele libera o espaço executável. Isso limpa a pilha. Ele recupera a memória global. Ele também recicla quaisquer alocações de heap pendentes.
Então, não há problema em vazar memória no final de um programa? Tecnicamente, sim. O sistema operacional cuida da limpeza. Não há consequências a longo prazo para a máquina.
Mas é uma má forma. E o mais importante: vazamentos de memória durante a execução são mortais. Se você continuar alocando sem liberar, você privará seu próprio processo. A pilha se enche. Novas alocações falham. O programa morre por asfixia, não por acidente.
Ponteiros versus os valores que eles apontam
Os ponteiros podem ser confusos. Eles são distintos dos valores aos quais fazem referência. Veja como isso funciona na prática.
Primeiro cenário. Você aloca um bloco. Você aponta duas variáveis para ele.
Saída:
10
20
Por que? p e q são ponteiros diferentes. Mas q = p os faz apontar para o mesmo bloco de memória. Alterar o valor através de p altera-o para q. Alterá-lo através de q altera-o para p. São duas maçanetas na mesma porta.
Segundo cenário. Você aloca dois blocos separados.
Saída:
20
Aqui, p e q apontam para locais diferentes. *q contém 20. *p = *q copia o valor 20 no local para o qual p aponta. O ponteiro p em si não se move. O valor no endereço de p muda.
A distinção é importante. Altera-se o endereço armazenado na variável. O outro altera os dados nesse endereço. Confundir os dois leva a erros lógicos que são mais difíceis de depurar do que erros de sintaxe.


Ponteiros, memória e queda livre
A saída é 20. A linha 6 diz tudo.
Você pode pensar que *p = *q e p = q fazem a mesma coisa. Eles não. Um move dados. O outro move endereços. O compilador permite atribuir *p = *q porque ambos os ponteiros desreferenciados são inteiros. É uma troca direta de valores. Mas p = q? Isso é diferente. Ele aponta p exatamente para o mesmo bloco de memória que q. O endereço muda. Os dados permanecem inalterados.
Se os tipos não corresponderem, o compilador o bloqueará. Um ponteiro para um número inteiro não pode apontar para uma sequência de caracteres. Os tipos devem estar alinhados.
Quatro maneiras de inicializar
Uma nova variável de ponteiro é um passivo até ser inicializada.
int *p; cria um ponteiro que não aponta para nenhum lugar específico. Aponta para o lixo. Desreferenciar é um erro. Você precisa de um local conhecido. Existem quatro maneiras de chegar lá.
- malloc : Aloca um bloco no heap.
pagora contém um endereço válido. O ponteiro é inicializado porque aponta para um pedaço específico e reservado de memória. - Atribuição :
p = q;seqfor válido,pse torna válido. Ele herda o endereçoq. Seqfor lixo,ptambém será lixo. - Endereço explícito : Você pode apontar
pdiretamente para uma variável conhecida.p = &i;apontappara o inteiroi. Simples. Direto. - Zero/NULO :
p = 0;oup = NULO;. Isso define o endereço como zero. Não aponta para um bloqueio. Ele aponta para o local nulo.
Por que usar zero? É uma bandeira.
Você pode verificar if (p == 0) para ver se o ponteiro é válido. O sistema reconhece isso. Se você tentar desreferenciar um ponteiro nulo, o programa travará. Duro.
p = 0; *p = 5;
A segunda linha falha. p aponta para nada. Você não pode escrever para nada. Esse comportamento é crítico para listas vinculadas posteriormente. É uma rede de segurança.
Liberando a pilha
Alocar memória não é suficiente. Você tem que devolver.
malloc ocupa espaço. free retorna.
free(p) faz duas coisas. Primeiro, ele desreserva o bloco. A pilha recupera essa memória. Pode ser reutilizado. Em segundo lugar, p não foi inicializado. Não se torna magicamente seguro. Você deve reinicializar p antes de usá-lo novamente.
O bloco desapareceu do seu escopo. O ponteiro está pendurado.
Aqui está o padrão padrão para lidar com um único número inteiro no heap:
Isto é principalmente uma demonstração. Mas mostra o ciclo de vida.
sizeof(int) retorna o tamanho em bytes. Na maioria das máquinas, isso é 4. Você pode codificar malloc(4). Não. sizeof torna o código portátil. Lê melhor. Ele se adapta se a arquitetura mudar.
malloc retorna um ponteiro genérico. Os compiladores geralmente avisam se você não o lança. (int *) converte esse blob genérico em um ponteiro para um número inteiro. Corresponde a p.
free(p) devolve o bloco ao heap.
Estruturas e precedência de operadores
As estruturas se comportam da mesma maneira. Você aloca um bloco para toda a estrutura, não apenas para os campos.
Veja (*p).i = 10;.
Por que os parênteses?
Porque . tem precedência maior que *.
Sem parênteses, *pi significa *(pi). Ele tenta acessar i no ponteiro p e então desreferencia-lo. Não é isso que você quer. Você deseja desreferenciar p primeiro e depois acessar i.
As regras de precedência são rígidas. * vincula-se com mais força do que . neste contexto. Os parênteses forçam a desreferência a acontecer antes do acesso do membro.
A maioria das pessoas odeia digitar (*p).i repetidamente. É detalhado.
C tem uma abreviação.
p->eu
É exatamente o mesmo que (*p).i. Mas é mais rápido digitar. Você verá -> em todo o código C. É a maneira padrão de acessar membros da estrutura por meio de um ponteiro.
O heap é um recurso finito. Aloque com cuidado. Desaloque de forma consistente. Ou observe sua memória vazar.


























