Arrays e ponteiros na prática
Esta aula explora a relação entre arrays e ponteiros em C, focando no decay de arrays para ponteiros, situações onde eles diferem, e o comportamento do operador sizeof. Inclui exemplos práticos para solidificar o entendimento.
Em C, arrays e ponteiros estão intimamente relacionados, mas não são a mesma coisa. Entender essa relação é crucial para escrever código correto e eficiente. Nesta aula, vamos explorar o conceito de decay de arrays para ponteiros, situações onde arrays e ponteiros se comportam de forma diferente, e como o operador sizeof se comporta com cada um.
O nome de um array pode ser usado como um ponteiro para seu primeiro elemento na maioria dos contextos, mas essa equivalência não é absoluta. Vamos detalhar cada aspecto com exemplos práticos.
Decay para ponteiro
Em C, o nome de um array decai para um ponteiro para seu primeiro elemento na maioria das expressões. Isso significa que, ao passar um array para uma função, o que é passado é um ponteiro, não uma cópia do array. Esse comportamento é chamado de decay.
Por exemplo, ao declarar int arr[5] = {1,2,3,4,5};, o nome arr em uma expressão como arr + 1 se torna um ponteiro para arr[0]. A aritmética de ponteiros então avança para o próximo elemento. Esse decay permite que arrays sejam manipulados como ponteiros, mas também pode causar confusão.
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // arr decai para ponteiro
printf("Primeiro elemento: %d\n", *p); // 10
printf("Segundo elemento: %d\n", *(p+1)); // 20
return 0;
}Quando diferem
Embora arrays e ponteiros sejam frequentemente intercambiáveis, existem situações onde eles se comportam de forma diferente. A diferença mais notável ocorre com o operador sizeof e com o operador de endereço &.
Quando aplicamos sizeof a um array, obtemos o tamanho total do array em bytes. Já em um ponteiro, obtemos o tamanho do ponteiro (geralmente 8 bytes em sistemas 64 bits). Além disso, o operador & aplicado a um array retorna um ponteiro para o array inteiro (tipo int (*)[5]), enquanto aplicado a um ponteiro retorna o endereço da variável ponteiro.
#include <stdio.h>
int main() {
int arr[5] = {1,2,3,4,5};
int *p = arr;
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 20 (5 * 4)
printf("sizeof(p) = %zu\n", sizeof(p)); // 8 (ou 4 em 32 bits)
printf("&arr = %p\n", (void*)&arr);
printf("&p = %p\n", (void*)&p);
return 0;
}sizeof em arrays vs ponteiros
O operador sizeof é uma das principais maneiras de distinguir arrays de ponteiros. Quando aplicado a um array declarado localmente, sizeof retorna o número total de bytes ocupados pelo array. Isso é útil para calcular o número de elementos: sizeof(arr) / sizeof(arr[0]).
No entanto, quando um array é passado para uma função, ele decai para um ponteiro, e sizeof dentro da função retornará o tamanho do ponteiro, não do array original. Isso é uma fonte comum de erros. Por isso, ao passar arrays para funções, geralmente passamos também o tamanho como parâmetro.
#include <stdio.h>
void printArray(int arr[]) {
// arr aqui é um ponteiro, não um array
printf("sizeof(arr) na função: %zu\n", sizeof(arr)); // 8
}
int main() {
int arr[10] = {0};
printf("sizeof(arr) no main: %zu\n", sizeof(arr)); // 40
printArray(arr);
return 0;
}Exemplos
Vamos consolidar com exemplos práticos que mostram a diferença entre arrays e ponteiros, especialmente em relação ao decay e sizeof.
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
// Acessando elementos via array e ponteiro
printf("arr[2] = %d\n", arr[2]); // 3
printf("*(ptr+2) = %d\n", *(ptr+2)); // 3
// Diferença com &
printf("&arr = %p\n", (void*)&arr); // endereço do array
printf("&ptr = %p\n", (void*)&ptr); // endereço do ponteiro
// Aritmética de ponteiros
printf("ptr+1 aponta para: %d\n", *(ptr+1)); // 2
// Array bidimensional
int matriz[2][3] = {{1,2,3},{4,5,6}};
int (*pmat)[3] = matriz; // ponteiro para array de 3 ints
printf("matriz[1][2] = %d\n", matriz[1][2]); // 6
printf("*(*(pmat+1)+2) = %d\n", *(*(pmat+1)+2)); // 6
return 0;
}Boas práticas
Ao trabalhar com arrays e ponteiros, lembre-se de: (1) usar sizeof apenas no escopo onde o array foi declarado; (2) passar o tamanho como parâmetro em funções; (3) preferir a notação de array para clareza quando possível; (4) cuidado com aritmética de ponteiros fora dos limites.
Referências
- cppreference: Array declaration
- cppreference: Pointer declaration
- GNU C Manual: Pointers and Arrays
- learn-c.org: Pointers
- Wikipedia: C arrays and pointers
Exercícios
Explique o que é o decay de array para ponteiro em C. Dê um exemplo.
✓ Resposta: Decay é o fenômeno pelo qual o nome de um array é convertido em um ponteiro para seu primeiro elemento na maioria das expressões. Exemplo:int arr[5]; int *p = arr;—arrdecai para&arr[0].Qual a diferença entre
sizeof(arr)esizeof(ptr)quandoarré um array de 10 inteiros eptré um ponteiro para int?✓ Resposta:sizeof(arr)retorna 40 (10 * 4 bytes, supondo int de 4 bytes), enquantosizeof(ptr)retorna o tamanho do ponteiro (8 bytes em sistemas 64 bits).Por que ao passar um array para uma função, devemos também passar o seu tamanho?
✓ Resposta: Porque o array decai para um ponteiro, esizeofdentro da função retorna o tamanho do ponteiro, não do array original. Sem o tamanho, a função não sabe quantos elementos existem.O que retorna
&arre como isso difere dearr?✓ Resposta:&arrretorna um ponteiro para o array inteiro (tipoint (*)[5]), enquantoarrdecai para um ponteiro para o primeiro elemento (int *). Ambos têm o mesmo valor de endereço, mas tipos diferentes.Escreva um programa que declare um array de 5 inteiros e um ponteiro para int, e mostre a diferença entre
sizeofdo array e do ponteiro.✓ Resposta:#include <stdio.h> int main() { int arr[5] = {1,2,3,4,5}; int *ptr = arr; printf("sizeof(arr) = %zu\n", sizeof(arr)); printf("sizeof(ptr) = %zu\n", sizeof(ptr)); return 0; }