Avançar, recuar e comparar ponteiros — navegando pela memória com precisão
Você já sabe que um ponteiro armazena um endereço de memória. O que torna os ponteiros em C especialmente poderosos é que você pode fazer contas com esses endereços — somar, subtrair e comparar — de forma inteligente e segura.
int * avança 4 bytes, não 1.
Se um int ocupa 4 bytes e você tem um array int v[5], os elementos estão nos endereços:
| Elemento | Índice | Endereço (exemplo) | Offset em bytes |
|---|---|---|---|
v[0] | 0 | 0x1000 | 0 |
v[1] | 1 | 0x1004 | 4 |
v[2] | 2 | 0x1008 | 8 |
v[3] | 3 | 0x100C | 12 |
v[4] | 4 | 0x1010 | 16 |
Ao escrever p + 1, o compilador sabe que deve avançar 1 × sizeof(int) = 4 bytes, e não apenas 1 byte. Isso é a escala automática — você pensa em elementos, não em bytes.
p + n, p - n, p++, p--, p += n, p1 - p2 (distância entre dois ponteiros), e comparações p1 == p2, p1 < p2. Não são válidas: adição de dois ponteiros (p1 + p2), multiplicação ou divisão de ponteiros.
p++ e p--As operações mais comuns em aritmética de ponteiros são o incremento (++) e o decremento (--), que avançam ou recuam o ponteiro por um elemento:
#include <stdio.h>
int main() {
int v[] = {10, 20, 30, 40, 50};
int *p = v; // p aponta para v[0]
printf("*p = %d (endereço %p)\n", *p, p); // 10
p++; // avança 4 bytes → aponta para v[1]
printf("*p++ = %d (endereço %p)\n", *p, p); // 20
p++; // avança mais 4 bytes → v[2]
printf("*p++ = %d (endereço %p)\n", *p, p); // 30
p--; // recua 4 bytes → v[1]
printf("*p-- = %d (endereço %p)\n", *p, p); // 20
return 0;
}
| Tipo do ponteiro | Tamanho do tipo | Passo de p++ |
|---|---|---|
char * | 1 byte | avança 1 byte |
short * | 2 bytes | avança 2 bytes |
int * | 4 bytes | avança 4 bytes |
double * | 8 bytes | avança 8 bytes |
struct Aluno * | sizeof(Aluno) | avança o tamanho da struct |
p + n produz um novo ponteiro que aponta n elementos à frente de p, sem mover p:
#include <stdio.h>
int main() {
float notas[] = {6.5f, 7.0f, 8.5f, 9.0f, 5.5f};
float *inicio = notas; // aponta para notas[0]
printf("notas[0] = %.1f\n", *inicio);
printf("notas[2] = %.1f\n", *(inicio + 2)); // avança 2×4 = 8 bytes
printf("notas[4] = %.1f\n", *(inicio + 4)); // avança 4×4 = 16 bytes
/* inicio ainda aponta para notas[0] — não foi modificado */
printf("inicio ainda = %.1f\n", *inicio);
/* Navegando com += */
float *p = notas;
p += 3; // agora aponta para notas[3]
printf("após p+=3: %.1f\n", *p);
return 0;
}
*(p + n) é exatamente equivalente a p[n]. O compilador gera o mesmo código para ambas as notações. A notação de colchetes é apenas açúcar sintático — por baixo, é sempre aritmética de ponteiros.
int v[] = {10, 20, 30, 40, 50};
int *p = v;
/* Estas quatro formas são idênticas — geram o mesmo código */
printf("%d\n", v[2]); // notação de array com nome
printf("%d\n", p[2]); // notação de array com ponteiro
printf("%d\n", *(v + 2)); // aritmética com nome do array
printf("%d\n", *(p + 2)); // aritmética com ponteiro
Quando você subtrai dois ponteiros do mesmo tipo que apontam para posições dentro do mesmo array, o resultado é a distância em número de elementos (não em bytes) entre eles:
#include <stdio.h>
int main() {
int v[] = {10, 20, 30, 40, 50};
int *inicio = &v[0];
int *fim = &v[4];
printf("fim - inicio = %ld elementos\n", fim - inicio); // 4
printf("distância em bytes: %ld\n",
(char*)fim - (char*)inicio); // 16
/* Encontrar posição de um elemento */
int *p = &v[2];
printf("v[2] está na posição %ld do array\n", p - v); // 2
return 0;
}
p1 - p2 só é válida e significativa quando os dois ponteiros apontam para elementos do mesmo array (ou para uma posição além do último elemento). Subtrair ponteiros de variáveis não relacionadas pode produzir qualquer resultado.
Ponteiros do mesmo array podem ser comparados com ==, !=, <, >, <=, >=. Um ponteiro menor aponta para uma posição anterior na memória:
#include <stdio.h>
int main() {
int v[] = {10, 20, 30, 40, 50};
int *p = &v[1];
int *q = &v[3];
printf("p < q? %s\n", p < q ? "sim" : "não"); // sim
printf("p == q? %s\n", p == q ? "sim" : "não"); // não
/* Percorrer array com dois ponteiros convergindo */
int *esq = v;
int *dir = v + 4;
printf("\nPercurso convergente: ");
while (esq <= dir) {
printf("%d ", *esq);
esq++;
}
printf("\n");
return 0;
}
int *p = NULL;
if (p == NULL)
printf("ponteiro inválido — não use!\n");
else
printf("valor: %d\n", *p); // só desreferencia se não for NULL
Uma das aplicações mais comuns de aritmética de ponteiros é percorrer arrays de forma eficiente — especialmente em funções que recebem arrays como parâmetros:
void imprimirV1(int v[], int n) {
for (int i = 0; i < n; i++)
printf("%d ", v[i]);
printf("\n");
}
void imprimirV2(int *p, int n) {
int *fim = p + n;
while (p < fim)
printf("%d ", *p++);
printf("\n");
}
Ambas produzem o mesmo resultado. A versão com ponteiro usa *p++: primeiro lê *p (desreferencia) e depois incrementa o ponteiro — tudo numa única expressão.
*p++ em contexto de loop: Aqui o comportamento é bem definido. *p++ lê o valor em p e avança p para o próximo elemento. É o idioma canônico para percorrer arrays em C e é amplamente usado em código C de alto desempenho.
#include <stdio.h> /* Soma todos os elementos usando aritmética de ponteiros */ int somarArray(const int *p, int n) { int soma = 0; const int *fim = p + n; while (p < fim) soma += *p++; return soma; } /* Encontra o maior elemento, retorna ponteiro para ele */ int *encontrarMaior(int *p, int n) { int *maior = p; int *fim = p + n; p++; while (p < fim) { if (*p > *maior) maior = p; p++; } return maior; } /* Inverte o array no próprio lugar usando dois ponteiros */ void inverter(int *esq, int *dir) { while (esq < dir) { int temp = *esq; *esq = *dir; *dir = temp; esq++; dir--; } } int main() { int v[] = {3, 7, 1, 9, 4, 6, 2}; int n = 7; printf("Array: "); for (int i = 0; i < n; i++) printf("%d ", v[i]); printf("\n"); printf("Soma: %d\n", somarArray(v, n)); int *pm = encontrarMaior(v, n); printf("Maior: %d (posição %ld)\n", *pm, pm - v); inverter(v, v + n - 1); printf("Invertido: "); for (int i = 0; i < n; i++) printf("%d ", v[i]); printf("\n"); return 0; }
Strings em C são arrays de char terminados com '\0'. A aritmética de ponteiros é a base de praticamente todas as operações com strings na linguagem:
#include <stdio.h> /* Calcula o comprimento de uma string sem usar strlen */ int meuStrlen(const char *s) { const char *p = s; while (*p != '\0') // avança até o terminador nulo p++; return p - s; // distância em caracteres } /* Copia uma string sem usar strcpy */ void meuStrcpy(char *dest, const char *src) { while ((*dest++ = *src++) != '\0') // copia e avança os dois ponteiros ; } int main() { char saudacao[] = "Olá, C!"; printf("String: %s\n", saudacao); printf("Comprimento: %d\n", meuStrlen(saudacao)); char copia[20]; meuStrcpy(copia, saudacao); printf("Cópia: %s\n", copia); /* Avançando manualmente em uma string */ const char *p = "abcdef"; printf("\nCaracteres: "); while (*p) // *p != '\0' pode ser escrito como *p printf("%c ", *p++); printf("\n"); /* Pulando para a segunda palavra */ const char *frase = "Bom dia!"; const char *q = frase; while (*q != ' ') q++; // avança até o espaço q++; // pula o espaço printf("Segunda palavra: %s\n", q); return 0; }
while (*p): Como o terminador nulo '\0' tem valor inteiro 0 (falso), while (*p) é exatamente equivalente a while (*p != '\0'). É o padrão mais compacto para percorrer strings em C e aparece em praticamente toda biblioteca de string.
Aritmética de ponteiros é poderosa, mas silenciosamente perigosa quando usada fora dos limites válidos:
| Situação | Status | Consequência |
|---|---|---|
p aponta para v[0] a v[n-1] |
✅ Válido | Acesso normal |
p = v + n (um além do último) |
✅ Válido como endereço | Pode ser comparado, mas não desreferenciado |
p = v - 1 (antes do início) |
❌ Comportamento indefinido | Pode corromper dados adjacentes |
p = v + n + 1 (dois além do último) |
❌ Comportamento indefinido | Acessa memória fora do array |
| Subtrair ponteiros de arrays diferentes | ❌ Comportamento indefinido | Resultado sem sentido |
int v[5] = {10, 20, 30, 40, 50};
int *p;
/* ✅ Válido — percorrer com ponteiro sentinela (v+5) */
for (p = v; p < v + 5; p++)
printf("%d ", *p);
/* ❌ INVÁLIDO — desreferenciar v+5 é comportamento indefinido */
p = v + 5;
printf("%d\n", *p); // acessa memória além do array!
/* ❌ INVÁLIDO — ponteiro antes do início */
p = v - 1;
printf("%d\n", *p); // comportamento indefinido
ArrayIndexOutOfBoundsException, C silenciosamente acessa memória fora do array. O resultado pode ser um valor errado, corrupção de outra variável, ou segmentation fault — dependendo de onde a memória acessada está mapeada.
**)Um ponteiro pode apontar para outro ponteiro. Isso é usado em funções que precisam modificar o próprio ponteiro do chamador (não apenas o valor apontado), e em arrays de strings:
#include <stdio.h> #include <stdlib.h> /* Aloca um array e faz o ponteiro do chamador apontar para ele */ void alocarArray(int **pp, int n) { *pp = (int *) malloc(n * sizeof(int)); // modifica o ponteiro original if (*pp == NULL) return; for (int i = 0; i < n; i++) (*pp)[i] = (i + 1) * 10; } int main() { int *vetor = NULL; alocarArray(&vetor, 5); // passa &vetor → função recebe int ** if (vetor != NULL) { printf("Array alocado: "); for (int i = 0; i < 5; i++) printf("%d ", vetor[i]); printf("\n"); free(vetor); } /* Array de strings — cada elemento é um char* */ const char *dias[] = { "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo" }; int total = 7; printf("\nDias da semana:\n"); for (int i = 0; i < total; i++) printf(" dias[%d] = %s\n", i, dias[i]); return 0; }
| Expressão | Tipo | Significado |
|---|---|---|
int x |
int |
Variável inteira |
int *p |
int * |
Ponteiro para int |
int **pp |
int ** |
Ponteiro para ponteiro para int |
*pp |
int * |
O ponteiro apontado por pp |
**pp |
int |
O int apontado pelo ponteiro apontado por pp |
| Aspecto | C | Java |
|---|---|---|
| Aritmética de ponteiros | ✅ Totalmente suportada — p++, p+n, p1-p2 |
❌ Não existe — Java não expõe endereços de memória |
| Acesso a array por índice | v[i] equivale a *(v+i) |
v[i] — sem equivalência com ponteiros |
| Verificação de limites | ❌ Nenhuma — o programador é responsável | ✅ ArrayIndexOutOfBoundsException automático |
| Percorrer string | while (*p++) com ponteiro de char |
for (char c : str.toCharArray()) |
| Distância entre elementos | p2 - p1 em número de elementos |
Não aplicável — sem aritmética de referências |
int v[] = {1, 2, 3, 4, 5};
int *p = v;
int *fim = v + 5;
while (p < fim)
printf("%d ", *p++);
int[] v = {1, 2, 3, 4, 5};
for (int x : v)
System.out.print(x + " ");
// Java abstrai completamente
// a posição na memória