Constants & const
A constant is a value that doesn't change while the program runs. You've already been writing constants without necessarily naming them — the 10 in int x = 10; is a literal constant. This page is about naming constants so your code is easier to read and maintain, and about the two very different tools C gives you for that: the preprocessor's #define and the const keyword.
Literal constants — a quick recap
Literal constants are values written directly into your source code: 42, 3.14, 'A', "hello". They have no name — they're just values sitting in the middle of an expression. The rest of this page covers ways to give a value a name so it can be reused and understood.
#define — the old-school macro constant
#define is a preprocessor directive. Before the compiler ever sees your code, the preprocessor performs a pure text substitution: every occurrence of the defined name is replaced with its replacement text.
#include <stdio.h>
#define MAX_STUDENTS 30
#define PI 3.14159
int main(void) {
int roster[MAX_STUDENTS];
double area = PI * 5 * 5;
printf("Roster size: %d\n", MAX_STUDENTS);
printf("Area: %f\n", area);
return 0;
}Because this is textual substitution and happens before compilation, MAX_STUDENTS has no type and no memory address — it simply doesn't exist anymore by the time the compiler runs; the compiler only ever sees 30. This also means a debugger can't show you the value of MAX_STUDENTS by name, and the compiler can't give you type errors if you misuse it.
const — a type-safe, read-only variable
The const qualifier is the more modern approach. It creates an actual, typed variable that the compiler will refuse to let you modify after initialization:
#include <stdio.h>
int main(void) {
const int max_students = 30;
const double pi = 3.14159;
printf("Roster size: %d\n", max_students);
max_students = 35; /* compile-time error: assignment of read-only variable */
return 0;
}Because const variables have a real type, the compiler can catch mistakes that #define can't — for example, passing a const int where a function expects a double * produces a proper type warning or error, whereas a careless macro substitution might compile and fail silently at runtime.
const int SIZE = 10;
int arr[SIZE]; /* rejected by strict C89 compilers -- SIZE is not a
constant expression, even though it can never change */
#define SIZE2 10
int arr2[SIZE2]; /* always fine -- this is genuinely replaced with 10
before the compiler even looks at it */Modern C (C99 and later) actually allows this particular case because of variable-length arrays, but relying on that is non-portable to strict C89 environments. When you need a value that is guaranteed to work as an array size or case label everywhere, #define or enum are the safer choices.
#define vs const at a glance
Aspect | #define | const |
|---|---|---|
Processed by | Preprocessor (text substitution) | Compiler (real variable) |
Has a type | No | Yes |
Type checking | None | Full |
Visible to a debugger | No | Yes |
Valid array size (strict C89) | Yes | No |
Occupies memory | No | Usually yes (unless optimized away) |
Scope rules | None -- applies everywhere after the #define | Normal C scope rules apply |
A third option: enum
C also lets you define a whole group of related named integer constants at once using enum, which gets its own dedicated page later. As a preview:
enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };
/* MONDAY is 0, TUESDAY is 1, and so on, unless you assign values explicitly */For a single value, #define or const is usually the right tool. For a related family of named integer values, enum tends to produce clearer, more self-documenting code.