Ponteiros, Referências e Memória Dinâmica
Estruturas dinâmicas só são possíveis porque um programa consegue guardar endereços, acessar dados indiretamente e solicitar memória durante a execução. Nesta aula, vamos transformar conceitos abstratos como ponteiro, referência, heap, alocação e desalocação em operações visuais.
Endereços como dados
Ponteiros e Referências
Uma variável comum guarda um valor. Um ponteiro guarda um endereço de memória. Isso permite que um programa encontre outro dado mesmo quando ele está em uma região distante da memória. Ao seguir esse endereço, realizamos uma desreferenciação: usamos o endereço para chegar ao valor.
Variável comum
Em int a = 42;, o nome a
é usado para acessar o valor armazenado em uma posição de memória.
Ponteiro
Em C/C++, int *p = &a; faz
p receber o endereço de
a. O operador
* permite acessar o dado apontado.
Referência
O termo “referência” depende da linguagem. Em C++, uma referência funciona como um alias para um objeto existente. Em Java, Python e outras linguagens, referências são abstrações usadas para acessar objetos sem expor diretamente a aritmética de endereços.
O operador de endereço &
Em C e C++, o operador & obtém o endereço de uma variável.
Se a está em 0x1000, então
p = &a faz p armazenar
0x1000. O ponteiro não copia o valor de a;
ele passa a indicar onde esse valor está.
Desreferenciação com *
Desreferenciar significa “seguir o endereço”. Se p contém
0x1000, a expressão *p acessa o conteúdo armazenado
em 0x1000. Por isso, atribuir *p = 99 modifica o
objeto apontado, e não o endereço guardado no ponteiro.
Ponteiro nulo não aponta para um objeto válido
Um ponteiro nulo representa a ausência de um alvo válido. Ele é útil para indicar estados como “não existe próximo nó” ou “nenhum objeto alocado”. Tentar desreferenciar um ponteiro nulo é um erro: não existe um objeto válido para acessar.
Memória durante a execução
Alocação e Desalocação Dinâmica
Nem sempre sabemos antes da execução quantos elementos serão necessários. A alocação dinâmica permite solicitar memória conforme a necessidade. Em linguagens de baixo nível, o programa também precisa definir quando aquela região deixa de ser necessária e pode ser desalocada.
Memória automática
Variáveis locais normalmente têm tempo de vida associado ao escopo da função. A criação e remoção são gerenciadas automaticamente conforme chamadas de função entram e saem da pilha.
Memória dinâmica
O heap é a região usada para alocações cujo tamanho ou tempo de vida precisa ser decidido durante a execução. A operação retorna uma referência ou endereço para o bloco reservado.
Tempo de vida
Uma variável e o objeto para o qual ela aponta podem ter tempos de vida diferentes. É justamente essa separação que possibilita estruturas dinâmicas — e também cria riscos como vazamentos e ponteiros pendentes.
Variáveis / Ponteiros
clique em um ponteiro para selecioná-loHeap
cada célula representa um bloco de memóriaAlocação: solicitar espaço no heap
Em C, funções como malloc solicitam uma quantidade de bytes e retornam um
ponteiro para a região reservada. Em C++, new cria um objeto em armazenamento
dinâmico e devolve um ponteiro. Conceitualmente, a operação é:
“encontre uma região livre, marque-a como ocupada e me diga onde ela começa”.
Desalocação: devolver espaço que não será mais usado
Quando o programa termina de usar uma região, ela deve voltar ao conjunto de memória
disponível. Em C usa-se free; em C++ clássico, delete ou
delete[]. Após a liberação, aquele endereço não representa mais um objeto
válido do programa.
Linguagens com coleta de lixo
Linguagens como Java e C# normalmente escondem a desalocação explícita do programador. Um coletor de lixo identifica objetos que deixaram de ser alcançáveis por referências e recupera seu espaço. A ideia de tempo de vida continua existindo; muda apenas quem decide quando a memória pode ser recuperada.
Quando endereço e tempo de vida se desencontram
Erros Clássicos de Memória
O perigo da memória dinâmica não está apenas em “ficar sem RAM”. O problema mais sutil é perder o controle sobre quem é dono de um bloco, quem ainda aponta para ele e se ele continua válido.
Memory Leak
A memória continua reservada, mas o programa perdeu todas as referências capazes de encontrá-la. O bloco não pode mais ser usado nem liberado pelo código que perdeu seu endereço.
p = malloc(...);p = NULL; // endereço perdido antes de free(p)
Dangling Pointer
O bloco já foi liberado, porém um ponteiro ainda guarda o endereço antigo. O endereço existe como número, mas já não representa um objeto válido.
free(p);// p ainda contém o endereço antigo
Double Free
O programa tenta liberar duas vezes a mesma alocação. Depois da primeira liberação, a região já voltou para o gerenciador de memória e pode até ter sido reutilizada.
free(p);free(p); // erro
Null Dereference
O programa tenta acessar o conteúdo de uma referência que não aponta para um objeto válido. Verificar nulidade antes do acesso é uma proteção básica em diversos contextos.
p = NULL;*p = 10; // inválido
Modelo mental
O Que Você Precisa Guardar
Ponteiros e memória dinâmica deixam de parecer “mágica” quando separamos três perguntas: onde está o objeto?, quem conhece seu endereço? e até quando aquele objeto continua válido?