const and Pointers

With pointers, const can protect two different things: the data being pointed to, or the pointer itself. Using const on pointer parameters documents which arguments a function only reads — and lets the compiler stop accidental changes.

Two places for const

DeclarationCan change the data?Can point elsewhere?
int *pyesyes
const int *p (same as int const *p)noyes
int *const pyesno
const int *const pnono

A trick for reading them: read the declaration right to left. const int *p is "p is a pointer to an int that's constant"; int *const p is "p is a constant pointer to an int".

C
#include <stdio.h>

int main(void) {
    int a = 1, b = 2;

    const int *read_only = &a;
    read_only = &b;        // fine: the pointer can move
    *read_only = 5;        // error: can't change the data through it

    int *const fixed = &a;
    *fixed = 5;            // fine: data can change
    fixed = &b;            // error: the pointer can't move

    printf("%d %d\n", a, b);
    return 0;
}
Compiler error
main.c:8:16: error: read-only variable is not assignable
main.c:12:11: error: cannot assign to variable 'fixed' with const-qualified type 'int *const'

const parameters

The most important use. A const pointer parameter promises the caller: "I'll only read this". The standard library does it everywhere — size_t strlen(const char *s) — and you should too:

C
#include <stdio.h>

double total(const double *prices, size_t n) {
    double sum = 0;
    for (size_t i = 0; i < n; i++) {
        sum += prices[i];
    }
    return sum;
}

void apply_discount(double *prices, size_t n, double percent) {
    for (size_t i = 0; i < n; i++) {
        prices[i] *= 1 - percent / 100;
    }
}

int main(void) {
    double basket[] = { 20.0, 15.5, 4.5 };
    printf("before: %.2f\n", total(basket, 3));
    apply_discount(basket, 3, 10);
    printf("after:  %.2f\n", total(basket, 3));
    return 0;
}
Output
before: 40.00
after:  36.00

Just from the signatures, a reader can tell that total leaves the array alone and apply_discount changes it.

The compiler enforces it

C
#include <stdio.h>

void print_upper(const char *s) {
    for (int i = 0; s[i]; i++) {
        if (s[i] >= 'a' && s[i] <= 'z') {
            s[i] -= 32;
        }
    }
    printf("%s\n", s);
}

int main(void) {
    char word[] = "hello";
    print_upper(word);
    return 0;
}
Compiler error
main.c:6:18: error: read-only variable is not assignable

Adding const is automatic, removing it isn't

You can pass a char * where a const char * is expected — adding a promise is always safe. Going the other way discards the promise, and the compiler warns:

C
#include <stdio.h>

void shout(char *s) {
    s[0] = 'H';
    printf("%s\n", s);
}

int main(void) {
    const char *msg = "hello";
    shout(msg);
    return 0;
}
Compiler warnings
main.c:10:11: warning: passing 'const char *' to parameter of type 'char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]

Here it would also be a real bug: msg points to a string literal, which can't be modified. Casting const away to silence the warning just hides the problem.

const for string literals

For historical reasons, string literals have type char[] in C, not const char[], so char *s = "text"; compiles without complaint — but modifying it is still undefined. Always write const char *s = "text"; and the compiler will protect you.

Const correctness pays off

  • Mark every pointer parameter const unless the function needs to modify the data.
  • Mark pointers to string literals const char *.
  • Use const for local values that shouldn't change after initialisation.

Once a code base is const-correct, the compiler catches a whole class of accidental modifications for free.

Try it yourself

Write count_char, which takes a const char * and counts how often a character appears, then use it on both an array and a string literal.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
6
4