Ponteiros em C¶
Explicação detalhada de ponteiros, um dos conceitos mais importantes da linguagem C.
Pré-requisito
Este post assume conhecimento básico de C. Veja a referência de C para revisar o básico.
O que é um ponteiro?¶
Um ponteiro é uma variável que armazena um endereço de memória. Em vez de guardar um valor diretamente (como int x = 10), o ponteiro guarda a localização onde esse valor está armazenado.
Por que usar ponteiros?¶
- Passar dados por referência - modificar variáveis dentro de funções
- Alocação dinâmica - criar estruturas de tamanho variável em tempo de execução
- Estruturas de dados - listas ligadas, árvores, grafos
- Performance - evitar cópia de grandes estruturas
- Arrays e strings - em C, são essencialmente ponteiros
Declaração e operadores¶
int x = 42;
int *ptr; // declara ponteiro para int (ainda não aponta para nada)
ptr = &x; // & = "endereço de" - ptr agora aponta para x
printf("%d\n", *ptr); // * = "valor apontado por" - imprime 42
Dois operadores fundamentais:
| Operador | Nome | Significado |
|---|---|---|
& |
Endereço de | Retorna o endereço de memória de uma variável |
* |
Desreferência | Acessa o valor no endereço apontado |
Visualizando na memória¶
Memória:
┌─────────┬─────────┬─────────┬─────────┐
│ ... │ 42 │ ... │ 0x100 │
└─────────┴─────────┴─────────┴─────────┘
↑ x ↑ ptr
0x100 0x200
x está no endereço 0x100 e contém 42
ptr está no endereço 0x200 e contém 0x100 (o endereço de x)
#include <stdio.h>
int main(void) {
int x = 42;
int *ptr = &x;
printf("Valor de x: %d\n", x); // 42
printf("Endereço de x: %p\n", &x); // 0x7fff... (varia)
printf("Valor de ptr: %p\n", ptr); // mesmo endereço
printf("Valor apontado: %d\n", *ptr); // 42
return 0;
}
Modificando valores através de ponteiros¶
Isso é útil para modificar variáveis dentro de funções:
// Sem ponteiro - não funciona
void incrementa(int n) {
n = n + 1; // modifica apenas a cópia local
}
// Com ponteiro - funciona
void incrementa_ptr(int *n) {
*n = *n + 1; // modifica o valor original
}
int main(void) {
int x = 10;
incrementa(x);
printf("%d\n", x); // ainda 10
incrementa_ptr(&x);
printf("%d\n", x); // agora 11
return 0;
}
Ponteiros e arrays¶
Em C, o nome de um array é um ponteiro para seu primeiro elemento:
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // equivalente a: int *ptr = &arr[0];
printf("%d\n", *ptr); // 10 (primeiro elemento)
printf("%d\n", *(ptr + 1)); // 20 (segundo elemento)
printf("%d\n", *(ptr + 2)); // 30 (terceiro elemento)
// Notação de array também funciona com ponteiros
printf("%d\n", ptr[0]); // 10
printf("%d\n", ptr[1]); // 20
printf("%d\n", ptr[2]); // 30
Visualização:
Aritmética de ponteiros¶
Quando você soma 1 a um ponteiro, ele avança para o próximo elemento do tipo, não para o próximo byte:
int arr[] = {10, 20, 30};
int *ptr = arr;
printf("%p\n", ptr); // 0x100 (exemplo)
printf("%p\n", ptr + 1); // 0x104 (avança 4 bytes, tamanho de int)
printf("%p\n", ptr + 2); // 0x108
// Diferença entre ponteiros
int *inicio = &arr[0];
int *fim = &arr[2];
printf("%ld\n", fim - inicio); // 2 (elementos, não bytes)
Ponteiros para ponteiros¶
Um ponteiro pode apontar para outro ponteiro:
int x = 10;
int *ptr = &x; // ponteiro para int
int **pptr = &ptr; // ponteiro para ponteiro para int
printf("%d\n", x); // 10
printf("%d\n", *ptr); // 10
printf("%d\n", **pptr); // 10
**pptr = 20; // modifica x
printf("%d\n", x); // 20
Visualização:
┌─────┐ ┌─────┐ ┌─────┐
│ 10 │ ←── │0x100│ ←── │0x200│
└─────┘ └─────┘ └─────┘
x ptr pptr
0x100 0x200 0x300
Uso comum: modificar um ponteiro dentro de uma função:
void aloca(int **ptr) {
*ptr = malloc(sizeof(int));
**ptr = 42;
}
int main(void) {
int *p = NULL;
aloca(&p);
printf("%d\n", *p); // 42
free(p);
return 0;
}
Ponteiros e strings¶
Strings em C são arrays de char terminados em \0. Podem ser manipuladas com ponteiros:
char str[] = "Hello";
char *ptr = str;
while (*ptr != '\0') {
printf("%c", *ptr);
ptr++;
}
// Imprime: Hello
Diferença importante:
char arr[] = "Hello"; // array - pode modificar
char *ptr = "Hello"; // ponteiro para literal - NÃO modifique
arr[0] = 'J'; // OK: arr agora é "Jello"
ptr[0] = 'J'; // ERRADO: comportamento indefinido (literal é read-only)
Ponteiros para funções¶
Funções também têm endereços. Ponteiros para funções permitem passar funções como argumentos:
#include <stdio.h>
int soma(int a, int b) { return a + b; }
int mult(int a, int b) { return a * b; }
// Função que recebe ponteiro para função
int opera(int x, int y, int (*func)(int, int)) {
return func(x, y);
}
int main(void) {
int (*ptr)(int, int); // declara ponteiro para função
ptr = soma;
printf("%d\n", ptr(3, 4)); // 7
ptr = mult;
printf("%d\n", ptr(3, 4)); // 12
// Usando a função opera
printf("%d\n", opera(5, 3, soma)); // 8
printf("%d\n", opera(5, 3, mult)); // 15
return 0;
}
Ponteiros e structs¶
Acessar membros de struct através de ponteiro:
struct Pessoa {
char nome[50];
int idade;
};
struct Pessoa p = {"Maria", 25};
struct Pessoa *ptr = &p;
// Duas formas equivalentes de acessar membros:
printf("%s\n", (*ptr).nome); // Maria
printf("%s\n", ptr->nome); // Maria (mais comum)
ptr->idade = 26; // modifica p.idade
O operador -> é açúcar sintático para (*ptr).membro.
Ponteiro nulo e ponteiro void¶
// Ponteiro nulo - não aponta para nada
int *ptr = NULL;
if (ptr == NULL) {
printf("Ponteiro nulo\n");
}
// Sempre verifique antes de desreferenciar
if (ptr != NULL) {
printf("%d\n", *ptr);
}
// Ponteiro void - ponteiro genérico
void *generic;
int x = 10;
float f = 3.14;
generic = &x;
printf("%d\n", *(int *)generic); // cast necessário
generic = &f;
printf("%.2f\n", *(float *)generic);
void * é usado em funções genéricas como malloc, qsort, bsearch.
Alocação dinâmica¶
Ponteiros são essenciais para alocar memória em tempo de execução:
#include <stdlib.h>
// Alocar um int
int *ptr = malloc(sizeof(int));
if (ptr == NULL) {
// erro de alocação
return 1;
}
*ptr = 42;
free(ptr);
// Alocar array de 10 ints
int *arr = malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++) {
arr[i] = i * 10;
}
free(arr);
// calloc - aloca e zera a memória
int *zeros = calloc(10, sizeof(int)); // 10 ints, todos 0
free(zeros);
// realloc - redimensiona
int *nums = malloc(5 * sizeof(int));
nums = realloc(nums, 10 * sizeof(int)); // agora tem espaço para 10
free(nums);
Erros comuns¶
Desreferenciar ponteiro não inicializado¶
Solução: sempre inicialize ponteiros:
Usar depois de free (use-after-free)¶
Solução: atribua NULL após free:
Vazamento de memória (memory leak)¶
void funcao(void) {
int *ptr = malloc(sizeof(int));
// esqueceu de chamar free(ptr)
} // ptr sai de escopo, memória vazou
Solução: sempre libere memória alocada:
Retornar ponteiro para variável local¶
Solução: use alocação dinâmica ou variável estática:
int *funcao(void) {
int *ptr = malloc(sizeof(int));
*ptr = 10;
return ptr; // OK, mas quem chamar deve dar free
}
Exemplo: lista ligada simples¶
#include <stdio.h>
#include <stdlib.h>
struct Node {
int valor;
struct Node *proximo;
};
// Inserir no início
void inserir(struct Node **cabeca, int valor) {
struct Node *novo = malloc(sizeof(struct Node));
novo->valor = valor;
novo->proximo = *cabeca;
*cabeca = novo;
}
// Imprimir lista
void imprimir(struct Node *cabeca) {
struct Node *atual = cabeca;
while (atual != NULL) {
printf("%d -> ", atual->valor);
atual = atual->proximo;
}
printf("NULL\n");
}
// Liberar lista
void liberar(struct Node *cabeca) {
struct Node *atual = cabeca;
while (atual != NULL) {
struct Node *temp = atual;
atual = atual->proximo;
free(temp);
}
}
int main(void) {
struct Node *lista = NULL;
inserir(&lista, 30);
inserir(&lista, 20);
inserir(&lista, 10);
imprimir(lista); // 10 -> 20 -> 30 -> NULL
liberar(lista);
return 0;
}
Resumo¶
| Conceito | Sintaxe | Significado |
|---|---|---|
| Declarar ponteiro | int *ptr; |
Ponteiro para int |
| Endereço de | &x |
Obtém endereço de x |
| Desreferência | *ptr |
Valor apontado por ptr |
| Ponteiro nulo | NULL |
Não aponta para nada |
| Ponteiro para ponteiro | int **pptr; |
Ponteiro para ponteiro |
| Membro via ponteiro | ptr->membro |
Acessa membro de struct |
| Aritmética | ptr + n |
Avança n elementos |