Embedded C

const Correctness

const looks like a small keyword, but it's a real, compiler-enforced promise — "this won't be modified through this name" — checked at compile time, not just a comment for other humans. On a microcontroller, that promise also has a physical consequence: const data can often live in flash instead of RAM, which on a device with far more flash than RAM is a genuinely significant space saving.

Quick reference
What it is
A type qualifier meaning "this value is not to be modified through this particular name" — enforced by the compiler as a real error, not a suggestion.
Mental model
A promise you make to the compiler, which it then checks on every line for you — the earliest, cheapest possible place to catch an accidental write to something that shouldn't change.
Key numbers
Two independent things a pointer can be const about: the pointer itself, and what it points to — four total combinations, covered below.
Reach for it when
A value genuinely never changes after initialization, or a function parameter is read-only and callers should be free to pass read-only data to it.

const on an ordinary variable

const int MAX_RETRIES = 3;
MAX_RETRIES = 5;   // COMPILE ERROR — assignment of read-only variable

Unlike a preprocessor #define, a const variable is a real, typed value the compiler is fully aware of — it can be type-checked, it has an actual address you can take with &, and it shows up properly in a debugger by name.

const and pointers — four combinations

A pointer has two separate things that can independently be const: the pointer's own value (which address it holds), and the data it points to. This is genuinely where most beginners' confusion with const comes from — there are four distinct combinations:

DeclarationMeaning
int *pNeither is const — can reassign p, can modify *p.
const int *pPointer to const — can reassign p to point elsewhere, cannot modify *p through it.
int *const pConst pointer — cannot reassign p, can freely modify *p.
const int *const pBoth — cannot reassign p, cannot modify *p either.
int x = 5, y = 10;

const int *p1 = &x;
p1 = &y;        // OK — p1 itself isn't const
// *p1 = 20;    // ERROR — the pointed-to int is const through p1

int *const p2 = &x;
*p2 = 20;       // OK — the int isn't const
// p2 = &y;     // ERROR — p2 itself is const, can't be reassigned

Reading a const pointer declaration

The reliable trick: read the declaration right to left starting from the variable name. const int *p1 reads as "p1 is a pointer to an int that is const" — the const applies to whatever is immediately to its right (here, effectively the int, since const int and int const mean the same thing). int *const p2 reads as "p2 is a const pointer to an int" — the const applies to p2 itself, immediately to its left this time.

Why const data can live in flash

A microcontroller's flash storage is typically far larger than its RAM, and flash is non-volatile — see ROM & Storage for that general distinction. Data declared const and initialized at compile time never needs to be writable, so many toolchains place it directly in flash instead of copying it into RAM at startup, freeing that RAM entirely:

const char version_string[] = "v2.4.1";     // can live entirely in flash
const uint16_t lookup_table[256] = { ... };  // a large table costing zero RAM

This is exactly the memory-hierarchy tradeoff introduced in Storage Classes & Scope's memory map — const global data typically lands in .rodata, distinct from .data's writable, RAM-resident globals.

Real-world applications

Gotchas

Field notes