C Const Conundrum
Tags: c
Published: 2024-01-30 01:32:57

---
title: C Const Conundrum
published: true
description: Descriptions of const and the new constexpr in C23, and when to use which.
tags: #c
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2024-01-29 14:28 +0000
---

## 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`:

1. `const` objects.
2. Pointers to `const`.

Neither is particularly complicated. However, `constexpr` was added in [C23](https://en.wikipedia.org/wiki/C23_(C_standard_revision)). Knowing which of the two to use in a particular case can be quite the conundrum.

> If this article seems like _d&eacute;j&agrave; vu_, it’s because I previously wrote about a [C++ Const Conundrum](https://dev.to/pauljlucas/const-conundrum-2bfj). 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”:

1. Whether it’s initialized at compile-time (vs. run-time) — _constant initialization_.

2. 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 `const` objects, except in C++, `const` for global objects is `static` by 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 either `static` or `extern`.

For example:

```c
// 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`:

```c
const int WEST = 180;
int const EAST = 0;         // Same as: const int.
```

The latter style is known as [east const](https://mariusbancila.ro/blog/2018/11/23/join-the-east-const-revolution/) 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](https://dev.to/pauljlucas/undefined-behavior-in-c-and-c-3a20):

```c
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:

```c
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`:

```c
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 `const` referred to as “const pointers.” The problem is that “const pointer” technically means the _pointer_ is `const`, _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:

```c
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:

```c
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:

```c
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](https://dev.to/pauljlucas/obscure-c99-array-features-3270) — something you probably don’t want.

The traditional work-arounds for lack of true “const-ness” for `const` are either to use a [macro](https://dev.to/pauljlucas/cc-preprocessor-macros-fh5) or an [enumeration](https://dev.to/pauljlucas/enumerations-in-c-ae7):

```c
#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:

```c
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:

```cpp
constexpr const int CECI = 42;  // const is pointless
```

## “Const Correctness”

You’ve likely heard the term [const correctness](https://isocpp.org/wiki/faq/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.

## References

+ [The `constexpr` specifier for object definitions](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm).