Ponteiros e const
Nesta aula, exploramos o uso de const com ponteiros em C, abrangendo ponteiro para const, const ponteiro e combinações. Aprendemos como essas declarações afetam a mutabilidade dos dados e do ponteiro, e discutimos boas práticas para escrever código mais seguro e expressivo.
Em C, o qualificador const pode ser aplicado a ponteiros de maneiras diferentes, controlando se o valor apontado pode ser modificado ou se o próprio ponteiro pode ser alterado. Entender essas variações é essencial para escrever código robusto, evitar efeitos colaterais indesejados e comunicar intenções claramente. Nesta aula, veremos três combinações principais: ponteiro para const, const ponteiro, e a combinação de ambos.
Esses conceitos são amplamente utilizados em APIs de bibliotecas, onde parâmetros de função são declarados como ponteiros para const para garantir que a função não modifique os dados passados. Além disso, o uso de const ajuda o compilador a otimizar o código e previne erros comuns de programação.
Ponteiro para const
Um ponteiro para const é um ponteiro que aponta para um valor constante. Isso significa que o valor apontado não pode ser modificado através do ponteiro, mas o ponteiro em si pode ser alterado para apontar para outro local.
A sintaxe é: const tipo *ptr; ou tipo const *ptr; (ambas equivalentes). Por exemplo:
int valor = 10;
const int *ptr = &valor; // ponteiro para const int
// *ptr = 20; // ERRO: não é permitido modificar o valor apontado
ptr = &outraVariavel; // OK: o ponteiro pode ser alteradoNote que a variável valor em si não é constante; apenas o acesso via ptr é restrito. Isso é útil para funções que desejam ler dados sem modificá-los.
const ponteiro
Um const ponteiro é um ponteiro que não pode ser alterado após a inicialização, ou seja, ele sempre apontará para o mesmo endereço. No entanto, o valor apontado pode ser modificado (a menos que também seja const).
A sintaxe é: tipo * const ptr;. Exemplo:
int valor = 10;
int * const ptr = &valor; // ponteiro constante
*ptr = 20; // OK: modifica o valor apontado
// ptr = &outro; // ERRO: não é possível alterar o ponteiroEsse tipo é frequentemente usado em hardwares embarcados para apontar para registradores de endereço fixo, ou em funções que não devem realocar o buffer.
Combinações
Podemos combinar as duas formas: const tipo * const ptr; — um ponteiro constante para um valor constante. Nesse caso, nem o ponteiro nem o valor apontado podem ser modificados.
Exemplo:
int valor = 10;
const int * const ptr = &valor;
// *ptr = 20; // ERRO
// ptr = &outro; // ERROHá também a possibilidade de usar const em ponteiros para ponteiros, como int ** const, int * const *, const int **, etc. A regra geral: o const se aplica ao que está imediatamente à sua esquerda, exceto se for o primeiro token, quando se aplica ao que está à direita. Por exemplo:
const int *p→ ponteiro para int constint const *p→ idemint * const p→ ponteiro const para intconst int * const p→ ponteiro const para int const
Boas práticas
- Use const sempre que possível: Isso torna o código mais seguro e auto-documentado. Funções que não modificam parâmetros devem declarar esses parâmetros como ponteiros para const.
- Prefira a ordem
const tipopara evitar ambiguidade: Emboratipo constseja equivalente, a formaconst tipoé mais comum e legível. - Cuidado com conversões: Você pode passar um ponteiro não-const para um parâmetro const (promoção segura), mas o contrário requer um cast explícito e é perigoso.
- Em APIs, use const para indicar que a função não altera os dados: Isso permite que o compilador otimize e que o usuário confie na função.
- Evite remover const com casts: A menos que absolutamente necessário, pois pode levar a comportamentos indefinidos se o objeto original for realmente constante.
Referências
- cppreference: const qualifier
- GNU C Manual: const
- IBM Documentation: const, volatile, restrict
- Stack Overflow: Difference between const int*, const int * const, and int const *
- GeeksforGeeks: Const Qualifier in C
Exercícios
Declare um ponteiro para const char que aponte para uma string literal "Hello". Tente modificar o caractere apontado e veja o erro do compilador. Escreva o código.
✓ Resposta:const char *str = "Hello"; // str[0] = 'h'; // ERRO: assignment of read-only location '*str'Declare um ponteiro constante para um inteiro, inicializado com o endereço de uma variável. Modifique o valor da variável através do ponteiro e tente alterar o ponteiro. Escreva o código.
✓ Resposta:int x = 5; int * const ptr = &x; *ptr = 10; // OK // ptr = &y; // ERRO: assignment of read-only variable 'ptr'Declare um ponteiro constante para um valor constante do tipo double. Inicialize com o endereço de uma variável double. Tente modificar o valor e o ponteiro. Escreva o código.
✓ Resposta:double valor = 3.14; const double * const ptr = &valor; // *ptr = 2.71; // ERRO // ptr = &outro; // ERROEscreva uma função que recebe um ponteiro para const int e imprime o valor. Em seguida, chame-a com um int não-const. Explique por que isso é permitido.
✓ Resposta:void print(const int *p) { printf("%d\n", *p); } int main() { int a = 10; print(&a); // OK: promove int* para const int* return 0; }É permitido porque a função promete não modificar o valor, e passar um ponteiro não-const é seguro (não quebra a promessa).
Identifique o tipo de cada declaração:
const int *p1,int * const p2,const int * const p3. Explique o que pode e o que não pode ser modificado.✓ Resposta:const int *p1: ponteiro para const int → pode modificar o ponteiro, não pode modificar o valor apontado.int * const p2: ponteiro const para int → pode modificar o valor apontado, não pode modificar o ponteiro.const int * const p3: ponteiro const para const int → não pode modificar nem o ponteiro nem o valor apontado.