Introduction
Originally, const was not part of C. (C++ added const and it was later back-ported to C89.)  In C, there are two uses for const:
- 
constobjects.
- Pointers to const.
Neither is particularly complicated. However, constexpr was added in C23. Knowing which of the two to use in a particular case can be quite the conundrum.
If this article seems like déjà vu, it’s because I previously wrote about a C++ Const Conundrum. However, this article is about C.
  
  
  const
As a refresher, here are details for the use of const in C. There are two parts to “const-ness”:
- Whether it’s initialized at compile-time (vs. run-time) — constant initialization. 
- It’s immutable after being initialized — immutability. 
An object declared const:
- Always means #2 above: it’s immutable. 
- Must be explicitly initialized in its definition. 
- May be initialized with a constant (compile-time) expression. 
- However, unlike C++, objects with static storage duration (those either at global scope or explicitly declared - static), must be initialized with a constant expression.
- Objects with dynamic storage duration (non- - static, function-local objects) may be initialized with a dynamic (run-time) expression.
- For global objects, is - extern(has external linkage) by default.
- May be explicitly declared - static(have internal linkage).
C++ also has
constobjects, except in C++,constfor global objects isstaticby default. If compatibility with C++ is required or you just want to be clear and not have to remember what the default linkage is in which language, always explicitly specify eitherstaticorextern.
For example:
// file.h
extern int const MAX;       // Declaration.
// file.cpp
int const MAX = 10;         // Definition.
int const ANSWER = 42;      // Initialized at compile-time.
int const SEED   = rand();  // Error in C; OK in C++.
int main() {
  int const SEED = rand();  // OK in both C and C++.
  // ...
}
Note that the compiler doesn’t care where you put const:
const int WEST = 180;
int const EAST = 0;         // Same as: const int.
The latter style is known as east const and it’s the style I prefer because then const is always making constant what’s to its immediate left (hence, the const is to the right or “east”). (This becomes relevant when pointers are involved.)
Attempting to modify a constant object results in undefined behavior:
int *p = (int*)&MAX;
*p = 43;                    // Undefined behavior.
  
  
  Pointers to const
A pointer to const can point to either a const or non-const object. The const means that you can’t modify the pointed-to object via that pointer. Whether the pointed-to object is actually const is irrelevant:
int i = 42;
int const *p = &i;  // pointer to const pointing at non-const
*p = 43;            // error: can't modify via pointer to const
Note that the pointer itself can also be const:
int *const cp = &i;         // constant pointer to non-const
int const *const cpc = &i;  // constant pointer to const
For a constant pointer, the const must appear to the right (“east”) of the *.
You’ll often read or hear pointers to
constreferred to as “const pointers.” The problem is that “const pointer” technically means the pointer isconst, not the pointed-to object. Therefore to be precise, I’ll use “pointer to const” unless I really mean “constant pointer.”
  
  
  const Isn’t Quite Constant
You might think that anything declared const is, well, constant.  The problem is that in some cases, the compiler treats const like it isn’t really constant.
The first case is that a const integer can not be used as a case label:
void f( int n ) {
  int const MAX = 10;
  switch ( n ) {
    case MAX:             // Error.
      // ...
Fortunately, you don’t run into this case that often.  However, the second, more common, and surprising case is that a const integer can not be used to specify an array’s size:
int const MAX = 10;
int array[MAX];           // Error.
Note that some compilers will accept the above code as an extension.
Inside a function, such code will be accepted, but for a different reason:
void f( void ) {
  int const MAX = 10;
  int array[MAX];         // OK, but VLA.
  // ...
}
The code is accepted not because the compiler considers MAX to be constant, but because array is a variable length array — something you probably don’t want.
The traditional work-arounds for lack of true “const-ness” for const are either to use a macro or an enumeration:
#define MAX1 10
int array1[MAX1];         // OK.
enum { MAX2 = 10 };
int array2[MAX2];         // OK.
Being forced to use either because const isn’t truly constant is just deficient. This is where constexpr comes in.
  
  
  constexpr
C23 adopted constexpr from C++ that’s a “const-er” const for objects. An object declared constexpr, like const:
- Must be explicitly initialized in its definition.
However, unlike const:
- Is always initialized at compile-time.
- Must be initialized with a constant expression.
- May not be explicitly declared extern(external linkage).
- Is itself a constant expression, i.e., is truly constant.
A constexpr object can be used in places where constant expressions are required such as the aforementioned case labels and array dimensions:
constexpr int MAX3 = 10;
int array3[MAX3];         // OK in C23.
Why didn’t the C Committee just “fix” const instead, i.e., make it truly constant in all cases?  Because they didn’t want to change the meaning of existing code nor increase the existing incompatibility of const between C and C++.
  
  
  No constexpr Functions
In C++, constexpr can also be used for functions; however, that ability was not adopted into C.  (Perhaps it will be in a future standard revision.)
Miscellaneous
Since a constexpr object is truly constant, adding const is pointless:
constexpr const int CECI = 42;  // const is pointless
“Const Correctness”
You’ve likely heard the term const correctness.  Historically, this involved only const.  With the addition of constexpr, does the meaning of const correctness change?
Yes.  Generally, constexpr should be preferred to const.  Use const only when you need dynamic initialization.
Conclusion
With the adoption of constexpr for objects, C finally fixed a long-standing language deficiency. Use constexpr whenever possible.
 

 
    
Top comments (2)
I never used the words "wrong" or "broken" in my article and only pointed out that
constdoesn't mean "constant" the way some people might think, i.e, immutable. To some, that's a deficiency. The rest of your comment is unsupported opinion.As for what the rationale for introducing
constexprwas, I'll let the original proposal speak for itself.As for your "maybe you can explain...," I have no idea what you're asking; but I see nothing wrong with
constexpras it stands.Having to use
enumto get a const literal is a defect.You seem to be one of those old-timers who thinks any change to C is heresy. Your irrelevant digressions into operator overloading strongly suggest that.
I never said anything about function parameters. You're just rambling.
We just obviously disagree. The C committee has made its decision. You're free to continue to yell at clouds. Have a nice day.
Some comments have been hidden by the post's author - find out more