A constant is a value with a name and a promise that it will not change. C gives you three mechanisms for that, and they are genuinely different things under the hood - one is a variable, one is text substitution, and one is an integer type.
Why Name a Value at All
Compare these two lines:
double area = 3.14159 * r * r;
double area = PI * r * r;
The second is better for three reasons that all show up later. It says what the number means. It appears in exactly one place, so changing the precision is one edit rather than a search. And a typo in PI is a compile error, where a typo in 3.14195 is a wrong answer.
Unnamed numbers scattered through code are called magic numbers, and removing them is one of the cheapest improvements you can make to a C program.
const
The const keyword marks a variable as read-only:
Uncomment the assignment and the compiler refuses: assignment of read-only variable 'PI'. That error at build time is the whole point.
A const variable is a real variable. It has a type, so the compiler checks how you use it. It obeys scope, so a const inside a function does not leak out. It has an address, so you can take a pointer to it. And a debugger can show you its name and value.
const must be initialized at the point of declaration - there is no second chance:
const int limit; /* error (or garbage forever): nothing can assign to it later */
const int limit = 50; /* correct */
#define
The preprocessor alternative is a macro:
This is not a variable. Before the compiler runs, the preprocessor replaces every occurrence of PI in the source with the characters 3.14159265358979. The compiler never sees the name at all - which is exactly why a #define has no type and cannot appear in a debugger.
Notice there is no semicolon and no =. A macro is raw text, so this common mistake:
#define MAX_USERS 100; /* note the semicolon */
int limit = MAX_USERS + 1; /* expands to: int limit = 100; + 1; -> error */
produces an error on a line that looks nothing like the definition. The uppercase naming convention for macros exists precisely so that readers know a substitution is happening.
Macros are conventionally written in UPPER_CASE; const variables usually follow it too when they stand in for the same idea.
Enum Constants
The third form gives you a set of named integers:
Values start at 0 and increase by one unless you assign them, so MON is 0 and FRI is 4. An anonymous enum { ... } is a common idiom for plain integer constants.
Enum constants have a real advantage over both alternatives: they are compile-time integer constants the compiler understands, and they are visible to the debugger. The full story is on enums in C.
Which One to Use
| Need | Use |
|---|---|
| A typed, scoped, debuggable constant | const |
An array size, a case label, a bitfield width | enum or #define |
| A string constant | #define or const char * |
| A floating-point constant | const double |
| A set of related integer values | enum |
Anything the preprocessor must see (#if, include guards) | #define |
The default should be const. Reach for #define when the value has to exist before compilation, and enum when you need a compile-time integer or a named group.
The one place this really bites is array sizes:
A const int in C is a read-only variable, not a compile-time constant expression. Inside a function C99 accepts int local[const_size] by making it a variable-length array; at file scope, in a struct, or as a case label it is rejected outright. enum and #define work everywhere.
(This is one of the real differences from C++, where const int is a constant expression. Code that moves between the languages trips on it.)
const With Pointers
const and pointers combine in three ways, and the meanings are not interchangeable. Read the declaration outward from the name:
const char *p; /* p points at chars that must not be modified */
char * const q; /* q itself cannot be re-pointed; the chars can change */
const char * const r; /* neither the pointer nor the chars can change */
The const char * form is the one you will write most, because it is the correct type for a function parameter that reads a string without changing it:
Adding const to a read-only parameter costs nothing and documents the contract in a way the compiler enforces. It is worth doing everywhere.
String Literals Are Effectively const
A subtle trap that const exists to catch:
char *s = "hello"; /* legal C, but s points into read-only memory */
s[0] = 'H'; /* undefined behavior - usually a crash */
String literals live in a read-only section of the executable. Writing through that pointer typically segfaults. Declaring the pointer const char *s = "hello"; makes the compiler reject the assignment instead of letting it crash at runtime.
If you need a modifiable string, copy it into an array:
Frequently Asked Questions
How do you declare a constant in C?
Add const to a declaration and give it a value: const double PI = 3.14159;. The compiler then rejects any attempt to assign to it. The older alternative is a preprocessor macro, #define PI 3.14159, which does text substitution instead.
What is the difference between const and #define in C?
const creates a real typed variable the compiler knows about, so it type-checks, appears in the debugger, and obeys scope. #define is pure text replacement done before compilation - it has no type, no scope, and cannot be inspected while debugging. Prefer const unless you need the value in a context that requires a compile-time constant.
Can a const variable be used as an array size in C?
Not for a fixed-size array at file scope. const int N = 10; int arr[N]; is a variable-length array in C99 (allowed inside a function, not at file scope, and not in a struct). For a genuine compile-time size, use #define N 10 or an enum { N = 10 };.
What does const char *p mean?
It is a pointer to a constant char: you may change where p points, but not the character it points at. char * const p is the opposite - a constant pointer to a modifiable char. const char * const p locks both. Read the declaration from the name outward.