Cconst & Pointers

const & Pointers

const and pointers combine in more than one way, and where you place the keyword changes what is actually being protected: the data being pointed to, the pointer variable itself, or both. Mixing these up is one of the most common sources of confusion in C, even for experienced programmers — but there is a reliable trick for reading any declaration correctly.

The four combinations

Declaration

Meaning

int *p;

Plain pointer. Both the pointed-to int and p itself can change.

const int *p;

Pointer to const data. The int through p cannot be changed (*p = 5; is an error), but p itself can be reassigned to point elsewhere.

int *const p;

Const pointer to non-const data. p itself cannot be reassigned once initialized, but the int it points to can be changed through it.

const int *const p;

Const pointer to const data. Neither the pointed-to int nor p itself can change. Maximum protection.

Reading declarations right-to-left
This trips up experienced C programmers too
`const int *p` and `int *const p` look almost identical but mean opposite things. The reliable way to tell them apart is to read the declaration starting at the variable name and moving outward (right-to-left, past each `*` and modifier).

For const int *p, start at p: "p is a pointer to an int that is const." The const binds to int, so it is the data that cannot be changed through p.

C
const int *p;
int value = 10;
p = &value;     // OK: reassigning p is allowed
// *p = 20;     // ERROR: cannot modify the int through p

For int *const p, start at p again: "p is a const pointer to an int." This time const binds to p itself, so the pointer cannot be reassigned, but the int it points to is fully mutable through it.

C
int a = 10, b = 20;
int *const p = &a;
*p = 99;        // OK: modifying the int through p is allowed
// p = &b;      // ERROR: cannot reassign a const pointer
A shortcut: read from the closest const to the name
Whichever side of the `*` a given `const` sits on tells you what it protects: `const` before the `*` (as in `const int *`) protects the data; `const` after the `*` (as in `int *const`) protects the pointer. `const int *const p` has both, so both are protected.
const on function parameters documents intent

Marking a pointer parameter as const is a common, valuable habit: it tells both the compiler and every future reader that the function promises not to modify the data through that pointer. The compiler will then flag any accidental write inside the function as an error, catching bugs before they ship.

C
#include <stdio.h>

// print_array only reads through arr — it should never write to it.
// const documents (and enforces) that promise.
void print_array(const int *arr, int length) {
    for (int i = 0; i < length; i++) {
        printf("%d ", arr[i]);
        // arr[i] = 0;   // ERROR: would violate the const promise
    }
    printf("\n");
}

int main(void) {
    int nums[3] = {1, 2, 3};
    print_array(nums, 3);
    return 0;
}
Standard library functions do this too
Functions like `strlen(const char *s)` and `printf`'s format string parameter use `const char *` for exactly this reason — it promises the input string will not be modified, and lets you safely pass string literals (which are themselves read-only) to them.
  • const before the * protects the pointed-to data; const after the * protects the pointer variable itself.

  • Read the declaration starting at the variable name and moving outward to figure out which one you have.

  • A const pointer parameter is a promise, checked by the compiler, that a function will not modify the data it points to.

  • You can always pass a non-const pointer where a const pointer parameter is expected — the reverse is not allowed.