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
| Declaration | Can change the data? | Can point elsewhere? |
|---|---|---|
int *p | yes | yes |
const int *p (same as int const *p) | no | yes |
int *const p | yes | no |
const int *const p | no | no |
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".
#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; }
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:
#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; }
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
#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; }
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:
#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; }
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
constunless the function needs to modify the data. - Mark pointers to string literals
const char *. - Use
constfor 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.
6 4