Ponteiros: introdução
Esta aula introduz o conceito de ponteiros em C, explicando o que são, como usar os operadores & e *, como declarar ponteiros e a importância do valor NULL. O conteúdo inclui exemplos práticos de código e exercícios com respostas colapsáveis.
Ponteiros são um dos recursos mais poderosos e fundamentais da linguagem C. Eles permitem manipular diretamente endereços de memória, possibilitando alocação dinâmica, passagem por referência e construção de estruturas de dados complexas. Nesta aula, vamos entender o que é um ponteiro, como usar os operadores & e *, como declará-los e a importância do valor NULL.
Dominar ponteiros é essencial para se tornar um programador C competente. Embora possam parecer confusos no início, com prática e exemplos claros você verá que são ferramentas naturais e extremamente úteis.
O que é um ponteiro
Um ponteiro é uma variável que armazena o endereço de memória de outra variável. Em vez de guardar um valor direto (como um inteiro ou caractere), um ponteiro guarda a localização onde esse valor está armazenado. Isso permite acessar e modificar indiretamente os dados.
Imagine a memória do computador como um grande armário com gavetas numeradas. Cada gaveta tem um endereço único. Uma variável comum guarda um objeto dentro da gaveta; um ponteiro guarda o número da gaveta onde o objeto está. Assim, você pode encontrar o objeto mesmo sem saber seu conteúdo atual.
#include <stdio.h>
int main() {
int x = 10; // variável comum
int *ptr; // declaração de ponteiro para inteiro
ptr = &x; // ptr recebe o endereço de x
printf("Valor de x: %d\n", x);
printf("Endereço de x: %p\n", (void*)&x);
printf("Valor de ptr: %p\n", (void*)ptr);
return 0;
}No exemplo, ptr armazena o endereço de x. A saída mostrará que ptr e &x têm o mesmo valor (o endereço de x).
Operadores & e *
O operador & (e-comercial) é chamado de operador de endereço. Quando aplicado a uma variável, retorna o endereço de memória onde essa variável está armazenada. Por exemplo, &x retorna o endereço de x.
O operador * (asterisco) é chamado de operador de desreferência (ou indireção). Quando aplicado a um ponteiro, ele acessa o valor armazenado no endereço apontado. Por exemplo, se ptr aponta para x, então *ptr equivale a x. Você pode ler ou modificar o valor através do ponteiro.
#include <stdio.h>
int main() {
int x = 10;
int *ptr = &x;
printf("x = %d\n", x); // 10
printf("*ptr = %d\n", *ptr); // 10
*ptr = 20; // altera x indiretamente
printf("x = %d\n", x); // 20
return 0;
}Note que *ptr = 20 modifica o valor de x, pois ptr aponta para x. Isso demonstra como ponteiros permitem modificar variáveis de outros escopos (útil em funções).
Declaração
Para declarar um ponteiro, usamos o tipo de dado que ele aponta, seguido de um asterisco (*) e o nome da variável. A sintaxe geral é: tipo *nome_do_ponteiro;. O asterisco indica que a variável é um ponteiro, não um valor comum.
Exemplos de declaração:
int *p; // ponteiro para inteiro
float *q; // ponteiro para float
char *r; // ponteiro para char
void *s; // ponteiro genérico (sem tipo específico)É importante inicializar o ponteiro antes de usá-lo. Um ponteiro não inicializado contém um valor lixo (endereço inválido), e desreferenciá-lo pode causar falhas de segmentação. A inicialização pode ser com o endereço de uma variável existente ou com NULL.
int a = 5;
int *ptr = &a; // inicializado com endereço de a
int *ptr2 = NULL; // inicializado com NULLNULL
NULL é uma constante simbólica definida em várias bibliotecas (como <stddef.h> ou <stdio.h>) que representa um ponteiro que não aponta para lugar algum. Na prática, seu valor é 0 (zero) quando convertido para um ponteiro. Usar NULL para inicializar ponteiros é uma boa prática, pois permite verificar se o ponteiro é válido antes de desreferenciá-lo.
Desreferenciar um ponteiro NULL (ou zero) é um comportamento indefinido e geralmente causa uma falha de segmentação (crash). Por isso, sempre verifique se um ponteiro não é NULL antes de usá-lo, especialmente em funções que recebem ponteiros como argumentos.
#include <stdio.h>
int main() {
int *ptr = NULL;
if (ptr != NULL) {
printf("%d\n", *ptr);
} else {
printf("Ponteiro é NULL, não é seguro desreferenciar.\n");
}
return 0;
}Além disso, a macro NULL é frequentemente usada para indicar que um ponteiro não foi atribuído ou que uma função não retornou um endereço válido (por exemplo, malloc retorna NULL em caso de falha).
Boas práticas
Sempre inicialize ponteiros. Se não tiver um endereço válido, atribua NULL. Verifique se o ponteiro não é NULL antes de desreferenciá-lo. Evite ponteiros selvagens (não inicializados). Lembre-se de que o operador & só pode ser aplicado a variáveis (l-values), não a expressões ou constantes.
Referências
- cppreference.com: Ponteiros em C
- GNU C Library: Ponteiros
- TutorialsPoint: Ponteiros em C
- Programiz: Ponteiros em C
- GeeksforGeeks: Introdução a Ponteiros
- Wikipedia: Ponteiro (programação)
Exercícios
Declare um ponteiro para inteiro chamado
pe atribua a ele o endereço de uma variávelainicializada com 42. Em seguida, imprima o valor deausando o ponteiro.✓ Resposta:#include <stdio.h> int main() { int a = 42; int *p = &a; printf("Valor de a: %d\n", *p); return 0; }Escreva um programa que declare duas variáveis inteiras
xey, e um ponteiroptr. Façaptrapontar parax, atribua 10 axatravés do ponteiro, depois façaptrapontar paraye atribua 20 ayatravés do ponteiro. Imprima os valores dexey.✓ Resposta:#include <stdio.h> int main() { int x, y; int *ptr; ptr = &x; *ptr = 10; ptr = &y; *ptr = 20; printf("x = %d, y = %d\n", x, y); return 0; }O que acontece se você tentar desreferenciar um ponteiro NULL? Escreva um pequeno programa que demonstre (cuidado: pode crashar). Em vez de executar, apenas explique o comportamento.
✓ Resposta:Desreferenciar um ponteiro NULL é um comportamento indefinido. Na maioria dos sistemas, isso resulta em uma falha de segmentação (segmentation fault) e o programa é encerrado abruptamente. O código abaixo ilustra, mas não execute em ambiente de produção:
int *ptr = NULL; printf("%d\n", *ptr); // crash!Explique a diferença entre
int *peint* p(espaço antes ou depois do asterisco). Isso faz diferença para o compilador?✓ Resposta:Não faz diferença para o compilador. Ambos declaram
pcomo um ponteiro para inteiro. O espaçamento é questão de estilo. No entanto, ao declarar múltiplas variáveis em uma linha, o asterisco se associa ao nome da variável, não ao tipo. Exemplo:int* p, q;declara p como ponteiro e q como inteiro. Por isso, muitos preferemint *p;para deixar claro que o asterisco faz parte do nome da variável.Escreva uma função
trocaque receba dois ponteiros para inteiros e troque os valores apontados. Use-a no main para trocar duas variáveis.✓ Resposta:#include <stdio.h> void troca(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; printf("Antes: x=%d, y=%d\n", x, y); troca(&x, &y); printf("Depois: x=%d, y=%d\n", x, y); return 0; }