Ponteiros e arrays: a relação fundamental
1. O Nascimento da Relação: Arrays São Ponteiros (Quase)
Em C, a relação entre ponteiros e arrays é tão íntima que muitos programadores iniciantes confundem os dois conceitos. A verdade é que, na maioria dos contextos, o nome de um array se comporta como um ponteiro constante para o primeiro elemento.
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // ptr aponta para arr[0]
printf("Valor de arr: %p\n", (void*)arr);
printf("Valor de ptr: %p\n", (void*)ptr);
printf("Valor de &arr[0]: %p\n", (void*)&arr[0]);
Os três endereços impressos serão idênticos. No entanto, existe uma diferença fundamental: sizeof se comporta de maneira distinta.
printf("sizeof(arr): %zu bytes\n", sizeof(arr)); // 20 bytes (5 * 4)
printf("sizeof(ptr): %zu bytes\n", sizeof(ptr)); // 8 bytes (tamanho do ponteiro)
Enquanto sizeof(arr) retorna o tamanho total do array (20 bytes para 5 inteiros), sizeof(ptr) retorna apenas o tamanho do ponteiro (8 bytes em sistemas 64-bit). Esta distinção é crucial e fonte frequente de bugs.
2. Indexação de Arrays: A Mão Invisível da Aritmética de Ponteiros
A notação arr[i] que usamos tão naturalmente é, na verdade, açúcar sintático para *(arr + i). O compilador traduz automaticamente a indexação para aritmética de ponteiros.
int arr[5] = {10, 20, 30, 40, 50};
// Forma tradicional com índice
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// Forma equivalente com aritmética de ponteiros
for (int i = 0; i < 5; i++) {
printf("*(arr + %d) = %d\n", i, *(arr + i));
}
A consequência mais surpreendente dessa equivalência é que i[arr] também funciona:
printf("arr[2] = %d\n", arr[2]); // 30
printf("2[arr] = %d\n", 2[arr]); // 30 também!
Isso acontece porque arr[2] é traduzido para *(arr + 2), e 2[arr] para *(2 + arr). A adição é comutativa, então ambos produzem o mesmo resultado.
3. Passando Arrays para Funções: A Decaída (Decay) para Ponteiro
Quando passamos um array para uma função, ele "decai" (decay) para um ponteiro para o primeiro elemento. Isso significa que as seguintes declarações de função são idênticas:
void func1(int arr[]) {
printf("sizeof(arr) dentro da funcao: %zu\n", sizeof(arr));
}
void func2(int *arr) {
printf("sizeof(arr) dentro da funcao: %zu\n", sizeof(arr));
}
Em ambos os casos, sizeof(arr) retornará o tamanho de um ponteiro, não do array original. Esta é a armadilha clássica:
void imprime_array(int arr[]) {
// ERRADO: sizeof(arr) é sizeof(int*), não o tamanho do array
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
printf("%d ", arr[i]);
}
}
A solução é passar o tamanho como parâmetro adicional:
void imprime_array(int arr[], int tamanho) {
for (int i = 0; i < tamanho; i++) {
printf("%d ", arr[i]);
}
}
4. Arrays Multidimensionais e Ponteiros: Desvendando a Matriz
Arrays bidimensionais como int mat[3][4] são armazenados como arrays de arrays na memória (linha a linha). O nome mat decai para um ponteiro para o primeiro elemento, que é um array de 4 inteiros.
int mat[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Ponteiro para array de 4 inteiros
int (*ptr)[4] = mat;
// Acessando elementos
printf("mat[1][2] = %d\n", mat[1][2]); // 7
printf("*(*(mat + 1) + 2) = %d\n", *(*(mat + 1) + 2)); // 7
printf("*(ptr[1] + 2) = %d\n", *(ptr[1] + 2)); // 7
A expressão *(*(mat + i) + j) funciona assim: mat + i avança i linhas (cada linha tem 4 inteiros), *(mat + i) obtém o array da linha i, e + j avança j elementos dentro dessa linha.
5. Ponteiros para Arrays vs. Arrays de Ponteiros: Não Confunda!
A diferença entre int (*ptr)[5] e int *arr[5] é sutil mas crucial:
// Ponteiro para array de 5 inteiros
int (*ptr_para_array)[5];
int array[5] = {1, 2, 3, 4, 5};
ptr_para_array = &array; // aponta para o array inteiro
// Array de 5 ponteiros para int
int *array_de_ponteiros[5];
int a = 10, b = 20, c = 30;
array_de_ponteiros[0] = &a;
array_de_ponteiros[1] = &b;
array_de_ponteiros[2] = &c;
Para criar uma matriz dinâmica, podemos usar um array de ponteiros:
int *matriz[3]; // array de 3 ponteiros
for (int i = 0; i < 3; i++) {
matriz[i] = (int*)malloc(4 * sizeof(int));
for (int j = 0; j < 4; j++) {
matriz[i][j] = i * 4 + j + 1;
}
}
// Acesso como matriz normal
printf("matriz[2][1] = %d\n", matriz[2][1]); // 10
6. Aplicações Práticas: Quando Usar Ponteiro, Quando Usar Array
A iteração com ponteiros geralmente é mais eficiente:
int arr[100000];
int *ptr;
// Iteração com índice
for (int i = 0; i < 100000; i++) {
arr[i] = i;
}
// Iteração com ponteiro (mais rápido)
for (ptr = arr; ptr < arr + 100000; ptr++) {
*ptr = 0;
}
Para strings, a diferença entre array e ponteiro é importante:
char str1[] = "Hello"; // Array mutável no stack
char *str2 = "World"; // Ponteiro para string literal (imutável!)
str1[0] = 'h'; // OK
// str2[0] = 'w'; // Comportamento indefinido! String literal é imutável
Na alocação dinâmica, usamos ponteiros como arrays flexíveis:
int *arr_dinamico = (int*)malloc(10 * sizeof(int));
if (arr_dinamico != NULL) {
for (int i = 0; i < 10; i++) {
arr_dinamico[i] = i * 2; // Usando notação de array
}
free(arr_dinamico);
}
Referências
- C Programming: Arrays and Pointers (cprogramming.com) — Tutorial completo explicando a relação entre arrays e ponteiros em C, com exemplos práticos de aritmética de ponteiros
- Pointers and Arrays in C (GeeksforGeeks) — Artigo detalhado sobre as diferenças entre ponteiros para arrays e arrays de ponteiros
- Array Decay in C (IBM Documentation) — Documentação oficial explicando o fenômeno de decay de arrays para ponteiros
- C Pointers and Arrays (TutorialsPoint) — Guia passo a passo sobre o uso de ponteiros com arrays unidimensionais e multidimensionais
- Multidimensional Arrays and Pointers (Learn-C.org) — Tutorial interativo sobre arrays multidimensionais e sua relação com ponteiros em C
- The C Programming Language (Kernighan & Ritchie) — Capítulo 5: "Pointers and Arrays" do livro clássico que define a linguagem C