Aula 06 Memória

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.

Objetivo 01Entender endereço e ponteiro
Objetivo 02Diferenciar ponteiro e referência
Objetivo 03Alocar e liberar memória
01

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.

Pense em três níveis diferentes: nome da variável → identifica uma variável no código; endereço → identifica onde ela está na memória; valor → é o conteúdo armazenado naquele endereço. Um ponteiro guarda justamente o segundo nível.
Laboratório de ponteiros
p = &a → p guarda 0x1000; portanto *p lê o valor 42.
Valor

Variável comum

Em int a = 42;, o nome a é usado para acessar o valor armazenado em uma posição de memória.

Endereço

Ponteiro

Em C/C++, int *p = &a; faz p receber o endereço de a. O operador * permite acessar o dado apontado.

Alias

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.

02

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.

A ideia central é um ciclo: solicitar memória → receber um endereço → usar o bloco → liberar o bloco. O ponteiro é a ligação entre o código e a região alocada.
Stack

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.

Heap

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.

Lifetime

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.

Simulador de heap
Total 24 blocos
Ocupados 0
Livres 24
Vazamentos 0

Variáveis / Ponteiros

clique em um ponteiro para selecioná-lo
Nenhum ponteiro criado.

Heap

cada célula representa um bloco de memória
[sistema] Heap inicializado com 24 blocos livres.
Alocaçã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.

03

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
04

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?

Ponteiro Guarda um endereço.
Desreferenciar Segue o endereço até o dado.
Alocar Reserva memória e devolve uma referência.
Desalocar Encerra a validade daquele bloco.
Conexão com estruturas de dados: uma lista encadeada consegue crescer porque cada novo nó pode ser alocado dinamicamente. O nó anterior guarda um ponteiro/referência para o novo endereço. Ao remover um nó, ajustamos as referências e então seu espaço pode ser desalocado.