Aritmética de ponteiros
1. Conceitos Fundamentais da Aritmética de Ponteiros
A aritmética de ponteiros é um dos recursos mais poderosos e ao mesmo tempo mais perigosos da linguagem C. Diferentemente da aritmética comum, que opera sobre valores numéricos, a aritmética de ponteiros opera sobre endereços de memória, levando em consideração o tipo de dado apontado.
A base de todo o conceito está na relação entre endereços de memória e tipos de dados. Quando declaramos um ponteiro int *p, o compilador sabe que cada deslocamento de uma unidade corresponde a sizeof(int) bytes na memória. Isso significa que p + 1 não avança um byte, mas sim o tamanho completo de um inteiro (tipicamente 4 bytes).
A unidade fundamental de avanço é determinada por sizeof(tipo). Se temos um ponteiro para double, cada incremento avançará 8 bytes (em sistemas comuns). Essa característica torna a aritmética de ponteiros ideal para navegação em arrays, pois cada operação nos move exatamente para o próximo elemento.
2. Operações Básicas: Incremento e Decremento
As operações mais simples são ptr++ e ptr--, que deslocam o ponteiro para o próximo ou anterior elemento do array.
#include <stdio.h>
int main() {
int numeros[] = {10, 20, 30, 40, 50};
int *ptr = numeros; // aponta para numeros[0]
printf("Valor inicial: %d (endereco: %p)\n", *ptr, (void*)ptr);
ptr++; // avança para numeros[1]
printf("Apos ptr++: %d (endereco: %p)\n", *ptr, (void*)ptr);
ptr--; // volta para numeros[0]
printf("Apos ptr--: %d (endereco: %p)\n", *ptr, (void*)ptr);
return 0;
}
Com diferentes tipos de dados, o comportamento ilustra a importância do sizeof:
#include <stdio.h>
int main() {
int int_arr[] = {1, 2, 3};
char char_arr[] = {'A', 'B', 'C'};
double dbl_arr[] = {1.1, 2.2, 3.3};
int *iptr = int_arr;
char *cptr = char_arr;
double *dptr = dbl_arr;
printf("int: %p -> %p (diferenca: %ld bytes)\n",
(void*)iptr, (void*)(iptr + 1),
(long)((char*)(iptr + 1) - (char*)iptr));
printf("char: %p -> %p (diferenca: %ld bytes)\n",
(void*)cptr, (void*)(cptr + 1),
(long)((char*)(cptr + 1) - (char*)cptr));
printf("double: %p -> %p (diferenca: %ld bytes)\n",
(void*)dptr, (void*)(dptr + 1),
(long)((char*)(dptr + 1) - (char*)dptr));
return 0;
}
Cuidado fundamental: a aritmética de ponteiros não verifica limites. Avançar além do array ou recuar antes do início é comportamento indefinido.
3. Soma e Subtração de Inteiros com Ponteiros
As expressões ptr + n e ptr - n permitem avançar ou recuar múltiplos elementos de uma só vez:
#include <stdio.h>
int main() {
int arr[] = {100, 200, 300, 400, 500};
int *ptr = arr; // aponta para arr[0]
// ptr + 2 equivale a &arr[2]
printf("ptr + 2 = %d (esperado: 300)\n", *(ptr + 2));
// ptr + 4 equivale a &arr[4]
printf("ptr + 4 = %d (esperado: 500)\n", *(ptr + 4));
// Navegação em loop sem índice
for (int i = 0; i < 5; i++) {
printf("Elemento %d: %d\n", i, *(ptr + i));
}
// Diferença crucial: ptr + 1 vs (int*)ptr + 1
// Ambos são equivalentes quando ptr já é int*
printf("ptr + 1 = %p\n", (void*)(ptr + 1));
return 0;
}
A diferença entre ptr + 1 e (int*)ptr + 1 só se manifesta quando o ponteiro original é de um tipo diferente. Se ptr é char*, ptr + 1 avança 1 byte, enquanto (int*)ptr + 1 avançaria 4 bytes (tratando o endereço como se apontasse para inteiros).
4. Subtração entre Ponteiros
A subtração de dois ponteiros que apontam para elementos do mesmo array retorna a distância entre eles em número de elementos, não em bytes:
#include <stdio.h>
#include <stddef.h>
int main() {
int arr[] = {10, 20, 30, 40, 50, 60, 70};
int *p1 = &arr[1]; // aponta para 20
int *p2 = &arr[5]; // aponta para 60
// Calculando a distância
ptrdiff_t distancia = p2 - p1;
printf("p2 - p1 = %td elementos\n", distancia);
printf("arr[5] - arr[1] = %d\n", arr[5] - arr[1]);
// Verificando que p1 + distancia == p2
printf("p1 + distancia = %p\n", (void*)(p1 + distancia));
printf("p2 = %p\n", (void*)p2);
// Número total de elementos no array
ptrdiff_t total = &arr[6] - arr;
printf("Total de elementos: %td\n", total + 1);
return 0;
}
O resultado é do tipo ptrdiff_t, definido em <stddef.h>, que é um tipo inteiro com sinal capaz de armazenar a diferença entre dois ponteiros.
5. Comparação entre Ponteiros
Os operadores relacionais podem ser usados para comparar endereços dentro do mesmo array:
#include <stdio.h>
int main() {
int arr[] = {5, 10, 15, 20, 25};
int *inicio = arr;
int *fim = arr + 4; // aponta para o último elemento
// Comparações úteis
if (inicio < fim) {
printf("inicio (%p) esta antes de fim (%p)\n",
(void*)inicio, (void*)fim);
}
// Verificando se um ponteiro está dentro dos limites
int *ptr = arr + 2; // elemento 15
if (ptr >= inicio && ptr <= fim) {
printf("ptr esta dentro do array\n");
}
// Comparação com NULL
int *nulo = NULL;
if (nulo == NULL) {
printf("Ponteiro nulo detectado\n");
}
// Percorrendo com comparação
for (int *p = arr; p <= arr + 4; p++) {
printf("%d ", *p);
}
printf("\n");
return 0;
}
Importante: comparar ponteiros que não pertencem ao mesmo array (ou um elemento após o final) é comportamento indefinido.
6. Aritmética com Ponteiros Void e Ponteiros para Tipos Diferentes
Ponteiros do tipo void* são genéricos, mas não suportam aritmética diretamente porque o compilador não conhece o tamanho do tipo apontado:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40};
void *vptr = arr;
// Isto NÃO compila:
// vptr++; // erro: aritmética com void*
// Solução: cast para char* para navegação byte a byte
char *cptr = (char*)vptr;
printf("Primeiro byte do array: %d\n", (unsigned char)*cptr);
// Avançando byte a byte
for (int i = 0; i < sizeof(int) * 4; i++) {
printf("Byte %d: %02x\n", i, (unsigned char)*(cptr + i));
}
// Para voltar à aritmética normal, faça cast de volta
int *iptr = (int*)cptr;
printf("Primeiro inteiro: %d\n", *iptr);
return 0;
}
Cuidados com alinhamento: fazer cast de char* para int* pode resultar em acesso não alinhado, causando comportamento indefinido em algumas arquiteturas.
7. Aplicações Práticas e Armadilhas Comuns
Navegação bidimensional
#include <stdio.h>
int main() {
int matriz[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Percorrendo com ponteiro para array de 4 ints
int (*ptr_linha)[4] = matriz;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d ", *(*(ptr_linha + i) + j));
}
printf("\n");
}
return 0;
}
Diferença entre *ptr++ e (*ptr)++
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int *p = arr;
// *ptr++ : primeiro acessa o valor, depois incrementa o ponteiro
printf("*p++ = %d (p agora aponta para %d)\n", *p++, *p);
// (*ptr)++ : incrementa o valor apontado
printf("(*p)++: antes = %d\n", *p);
(*p)++;
printf("(*p)++: depois = %d\n", *p);
return 0;
}
Erros comuns
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
// ERRO 1: estouro de buffer
// for (int i = 0; i <= 5; i++) { // acessa arr[5], que não existe
// printf("%d ", *p++);
// }
// ERRO 2: confundir endereço com valor
printf("Endereco: %p\n", (void*)p); // correto
// printf("Endereco: %p\n", p); // também funciona, mas sem cast
// ERRO 3: subtração de ponteiros de arrays diferentes
int outro_arr[3] = {10, 20, 30};
// ptrdiff_t diff = p - outro_arr; // comportamento indefinido
// Forma correta de evitar problemas
p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
A aritmética de ponteiros é uma ferramenta poderosa que permite escrever código eficiente e elegante, mas exige disciplina e compreensão profunda da memória. Dominar esse conceito é essencial para programação em C de alto nível.
Referências
- C Pointers (GeeksforGeeks) — Guia completo sobre ponteiros em C, incluindo aritmética e exemplos práticos
- Pointer Arithmetic in C (TutorialsPoint) — Tutorial detalhado com todos os operadores de aritmética de ponteiros
- C Language Pointer Arithmetic (cppreference.com) — Documentação oficial da linguagem C sobre aritmética de ponteiros
- Pointers and Arrays (Learn-C.org) — Tutorial interativo sobre a relação entre ponteiros e arrays em C
- Understanding Pointer Arithmetic in C (Medium - C Programming) — Artigo técnico explicando os fundamentos e armadilhas da aritmética de ponteiros