devnotes.

Constants and Type Qualifiers

C offers several ways to define values that shouldn't change, each with different guarantees.

#define MAX_USERS 100        // preprocessor macro: pure text substitution, no type

const int maxRetries = 3;    // typed constant: checked by the compiler

enum Color { RED, GREEN, BLUE }; // named integer constants: RED=0, GREEN=1, BLUE=2

// C23: constexpr — a compile-time-evaluated, properly typed constant
constexpr int bufferSize = 256;

#define is handled entirely by the preprocessor before compilation even starts — it has no type and no scope, and errors involving it point to the expanded code, making mistakes harder to trace. const creates a real, typed variable that the compiler enforces as read-only; unlike #define, it respects scope and can be inspected by a debugger.

enum groups related named integer constants together, improving readability over raw "magic numbers" scattered through code. constexpr (new in C23) is generally the best choice for simple constant values going forward — it's type-checked like const, but guaranteed to be evaluated at compile time like a macro, combining the safety of one with the compile-time guarantee of the other.

The volatile qualifier tells the compiler a variable's value can change unexpectedly (e.g., a hardware register or a signal handler), so it must never optimize away reads of that variable — this matters mainly in embedded and systems programming.

C Constants and Type Qualifiers: #define, const, constexpr | DevNotes