C Mastery / Pointers Part 4: const, volatile, restrict
Part 1 — The Core Language

Pointers Part 4: const, volatile, restrict

This chapter covers the three type qualifiers as they interact with pointers: const, volatile, and restrict. volatile is one of the most misunderstood features in C, so it receives special attention.

Why This Matters

Qualifiers change what the compiler may assume. const documents and enforces read-only intent. volatile tells the compiler a value can change outside its control (hardware, signal handlers). restrict enables optimization by promising no aliasing. Misusing any of them produces either bugs or missed optimizations.

Prerequisites

Core Concept

const and pointers

There are four distinct combinations:

DeclarationMeaning
const int *ppointer to const int (the *int* is read-only)
int const *psame as above
int *const pconst pointer to int (the *pointer* is read-only)
const int *const pconst pointer to const int

Read right-to-left: const int *p is "p is a pointer to a const int."

int x = 5;
const int *p = &x;   /* ok: can't write through p */
/* *p = 10;          ERROR */
p = &y;              /* ok: the pointer itself is modifiable */

volatile

volatile tells the compiler that every access to the object must actually happen, in the order written, and must not be optimized away or cached in a register. It is for values that can change or be observed outside the compiler's model: memory-mapped hardware registers, variables shared with a signal handler, or memory modified by other threads (though it is *not* a synchronization primitive for threads — see below).

volatile int status;   /* every read/write is a real memory access */

restrict (C99)

restrict is a promise by the programmer that, for the lifetime of the pointer, no other pointer will be used to access the object it points to (with some precise exceptions). It enables the compiler to optimize aggressively because it can assume no aliasing.

void add_arrays(int *restrict dst, const int *restrict a,
                const int *restrict b, size_t n);

The compiler may assume dst, a, and b do not overlap, enabling vectorization and reordering.

Syntax

const int *p;
int *const p;
const int *const p;

volatile int *vp;
int *volatile pv;

int *restrict rp;

Examples

const pointer vs. pointer to const

int a = 1, b = 2;

const int *p = &a;   /* pointer to const: can rebind, can't write */
p = &b;              /* ok */
/* *p = 5; */        /* error */

int *const q = &a;   /* const pointer: can write, can't rebind */
*q = 5;              /* ok */
/* q = &b; */        /* error */

volatile for a hardware register

#define STATUS_REG ((volatile unsigned *)0x40000000u)

unsigned read_status(void)
{
    return *STATUS_REG;   /* always performs a real read */
}

(For a real embedded system, the address and width are platform-specific; this is illustrative of the *pattern*.)

restrict for non-overlapping arrays

#include <string.h>

void scale(int *restrict out, const int *restrict in, int factor, size_t n)
{
    for (size_t i = 0; i < n; i++)
        out[i] = in[i] * factor;
}

The restrict promises out and in do not overlap, so the compiler can vectorize the loop.

How It Works

const-qualified lvalue. (A const object may still be modified through a non-const alias, which is why const is not a guarantee of immutability.)

analysis.

Variations

volatile does NOT mean atomic

volatile does not make a variable atomic, does not prevent data races, and does not provide memory ordering for multithreaded code. Use _Atomic and atomics (c.conc.5) for that. VERIFIED

volatile for signal handlers

A variable shared between a signal handler and normal code should be sig_atomic_t (from <signal.h>) and volatile, for the specific case of flag-like communication. c.stdlib.9 covers this.

Common Mistakes

lvalue read-only).

Undefined Behavior

Portability

actually does with it is an implementation detail.

Under the Hood

const is usually erased in generated code (it is a compile-time property). volatile forces a load or store instruction with no caching in a register. restrict informs alias analysis, which feeds optimization passes like vectorization and instruction scheduling.

Practical Usage

pointers to read-only data.

non-overlap.

Exercises

1. Write each of the four const/pointer combinations and attempt both rebind and write; observe the compiler errors. 2. Write a program with a volatile variable and compile with and without optimization; inspect the generated assembly to see the difference. 3. Write a restrict-annotated function and a non-annotated version, and compare generated code or benchmark the difference. 4. Explain why volatile is not a substitute for atomics.

Deep Challenge

Write a small memory-mapped register abstraction using volatile that reads and writes a device register, and explain exactly what could go wrong if the volatile were omitted when the compiler optimizes. Then discuss the additional measures needed if the register is also accessed from an interrupt handler.

References

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.ptr.const06
c.ptr.volatile06
c.ptr.restrict05