The C language has an issue with constants. As far as I can tell, three different ways are at your disposal to express a constant: constant expressions, literal constants, and constant types. More variety may be available, which adds to the confusion.
Constant Expressions. Also (and inconsistently) called symbolic constants, these are created by using the precompiler #define directive. For example:
#define COUNT 30
Traditionally, these constants are created in all caps, as shown with COUNT above. What the precompiler does is to replace all instances of COUNT in the source code file with the value or expression specified. This approach truly makes the expression constant, in that once the word is transformed into its assigned value the program cannot alter the value (or expression).
I prefer use #define to set constants in my code. Though it’s not a requirement to make it all caps, doing so makes it easier for me to locate them. Writing these constants in all caps is also the tradition for symbolic constants defined in the various header files, such as NULL, FILE, and so on.
In a way, the constant expression is the true constant qualifier in C — and it’s not even part of the language.
Literal Constants. A literal constant is any value you set directly into the code. Literals cannot be changed. Hence, they’re constant. But they’re also one-shot deals: If you need to repeat a value in the code, you must type it again. If you need to edit the value, you must hunt down all instances and change the value consistently. Doing so is a pain.
Another problem with literal constants is that they occasionally become “magic numbers,” which lack an explanation or reference as to their underlying purpose. (Click the link for an example.)
Suffixes can be applied to some literal constants to confirm their values. These characters include:
Lorlfor a long integerUorufor an unsigned integerULorulfor a long unsigned integer
The L suffix can also be used as a prefix to specify a wide string: L"Wide string" Other prefixes include 0 (zero) for octal values and 0x for hexadecimal.
Character constants used in C include \n and \0 among many others. These represent values, not an assignable constant you can create.
Constant Types. A constant type is declared with the const qualifier. A data type and identifier (variable name) must be specified, along with an assigned value:
const int count = 30;
While this value cannot be altered in the code, it’s not technically a constant. The compiler flags any attempt to alter the value of count as a warning or error. According to a comment made by subscriber M.Stumpfl, the const qualifier should really be a “read only” qualifier. Effectively, the read-only condition is what the compiler applies to the value.
Various other online sources concur with sentiments about the const qualifier. In fact, you can write code that bypasses const, though I don’t recommend using it as the results are unpredictable:
2025_11_22-Lesson.c
#include <stdio.h>
int main()
{
const int count = 30;
int *c;
printf("The count is %d\n",count);
c = (int *)&count;
*c = 0;
printf("The count is %d\n",count);
return 0;
}
This code creates the constant count, assigned the value 30. The value is output, but then pointer c steals the address of variable count. Through the pointer, a new value is assigned to “constant” count: zero.
The program builds without warnings or errors. Here is the output when compiled with gcc:
The count is 30
The count is 0
Not so constant, eh?
Building with clang works, though the program doesn’t alter the value. This inconsistency is enough proof that the technique isn’t reliable. But that’s not even the point!
A better solution is offered in the C23 standard, the constexpr qualifier. I cover this new keyword in next week’s Lesson.