Em C, a palavra-chave const pode ser usada em conjunto com ponteiros para restringir a modificação do valor apontado ou do próprio ponteiro. Compreender essas variações é essencial para escrever código mais seguro e expressivo, especialmente em APIs e funções que não devem modificar dados passados como parâmetro. Nesta aula, exploraremos as três combinações principais e as boas práticas associadas.

Vamos começar com uma visão geral: um ponteiro é uma variável que armazena um endereço de memória. Quando adicionamos const, podemos proteger o valor apontado, o próprio ponteiro, ou ambos. A posição de const em relação ao * determina o que é constante.

Ponteiro para const

Um ponteiro para const é um ponteiro que aponta para um valor que não pode ser modificado através desse ponteiro. Ou seja, o valor apontado é tratado como constante. No entanto, o ponteiro em si pode ser alterado para apontar para outro local.

Sintaxe: const int *ptr; ou int const *ptr; (ambas equivalentes).

Exemplo:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 20;
    const int *ptr = &a;

    // *ptr = 30; // Erro: tentativa de modificar valor constante
    ptr = &b;    // OK: ponteiro pode ser alterado
    printf("%d\n", *ptr); // Imprime 20
    return 0;
}

Note que ptr aponta para um int constante, mas o próprio ptr não é constante. Isso é útil quando queremos garantir que uma função não modifique os dados apontados, como em strlen(const char *s).

const ponteiro

Um ponteiro constante é um ponteiro cujo valor (o endereço armazenado) não pode ser alterado após a inicialização. Ou seja, o ponteiro sempre apontará para o mesmo local de memória. No entanto, o valor apontado pode ser modificado (a menos que também seja const).

Sintaxe: int *const ptr;.

Exemplo:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 20;
    int *const ptr = &a;

    *ptr = 30;    // OK: modifica o valor de a
    // ptr = &b; // Erro: ponteiro é constante
    printf("%d\n", *ptr); // Imprime 30
    return 0;
}

Isso é comum em parâmetros de funções que precisam de um buffer fixo, ou em estruturas de dados que mantêm um ponteiro fixo para um array.

Combinações

Podemos combinar as duas formas para ter um ponteiro constante que aponta para um valor constante. Isso significa que nem o ponteiro nem o valor apontado podem ser modificados.

Sintaxe: const int *const ptr; ou int const *const ptr;.

Exemplo:

#include <stdio.h>

int main() {
    int a = 10;
    const int *const ptr = &a;

    // *ptr = 30; // Erro: valor constante
    // ptr = &b; // Erro: ponteiro constante
    printf("%d\n", *ptr); // Imprime 10
    return 0;
}

Essa combinação é usada quando queremos total imutabilidade, como em ponteiros para tabelas constantes ou em sistemas embarcados.

É importante lembrar que a regra de leitura da declaração pode ser feita da direita para a esquerda: const int *ptrptr é um ponteiro para int constante. int *const ptrptr é um ponteiro constante para int. const int *const ptrptr é um ponteiro constante para int constante.

Boas práticas

  • Use const sempre que possível para parâmetros de funções que não devem modificar os dados apontados. Isso documenta a intenção e permite que o compilador detecte erros.
  • Prefira const em declarações de ponteiros para strings (const char *) para evitar modificações acidentais.
  • Use ponteiros constantes (int *const) quando o endereço não deve mudar, como em iteradores ou buffers fixos.
  • Evite confusão com a sintaxe: lembre-se de que const à esquerda de * qualifica o valor apontado; à direita, qualifica o ponteiro.
  • Em APIs públicas, use const generosamente para indicar que a função não altera os dados de entrada.

Referências

Exercícios

  1. Explique a diferença entre const int *p e int *const p.

    ✓ Resposta: const int *p é um ponteiro para um inteiro constante: o valor apontado não pode ser modificado via p, mas p pode ser alterado para apontar para outro local. Já int *const p é um ponteiro constante para inteiro: o ponteiro em si não pode ser alterado (sempre aponta para o mesmo endereço), mas o valor apontado pode ser modificado.
  2. Dado o código abaixo, identifique os erros e corrija-os.

    int main() {
        int a = 5;
        const int *p = &a;
        *p = 10;
        return 0;
    }

    ✓ Resposta: O erro está na tentativa de modificar *p (linha 4). Como p é um ponteiro para const int, não se pode alterar o valor apontado. Para corrigir, remova a linha ou troque a declaração para int *p = &a;.
  3. Escreva uma declaração de ponteiro que seja constante e aponte para um valor constante do tipo double.

    ✓ Resposta: const double *const ptr; ou double const *const ptr;.
  4. O que será impresso pelo seguinte código? Justifique.

    #include <stdio.h>
    int main() {
        int x = 10, y = 20;
        int *const p = &x;
        *p = 30;
        p = &y;
        printf("%d %d\n", x, y);
        return 0;
    }

    ✓ Resposta: O código não compila devido ao erro na linha 6 (p = &y;), pois p é um ponteiro constante (int *const) e não pode ser alterado. Se essa linha for removida, imprimiria 30 20.
  5. Crie uma função void print_array(const int *arr, int size) que imprime os elementos de um array sem modificá-los. Implemente e teste com um array de exemplo.

    ✓ Resposta:
    #include <stdio.h>
    
    void print_array(const int *arr, int size) {
        for (int i = 0; i < size; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    }
    
    int main() {
        int arr[] = {1, 2, 3, 4, 5};
        print_array(arr, 5);
        return 0;
    }