# Symbolic Constant Conundrum

> Source: <https://dev.to/pauljlucas/symbolic-constant-conundrum-40e7>
> Published: 2026-05-23 00:34:36+00:00

## Introduction

A *symbolic constant* in any programming language is a name — a *symbol* — that can be used to stand in for a *constant* — a literal value. Programming languages inherited symbolic constants from mathematics that has many of them grouped by specific field of study. Examples include: π (pi), *c* (speed of light), e (Euler’s number), G (gravitational constant), *h* (Plank’s constant), etc. While those exact constants can be defined and used in programs, many programs define program-specific constants. Using constants is better than using [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)).

Both C and C++ have acquired multiple ways to specify symbolic constants as their respective languages have evolved over the decades, namely:

- Macros (via
`#define`

). - Enumerations.
-
`const`

. -
`constexpr`

.

Knowing which of the ways to use in a particular case can be quite the conundrum.

## Macros

Originally, C *only* had [macros](https://dev.to/pauljlucas/cc-preprocessor-macros-fh5), specifically, object-like macros, e.g.:

```
#define BUF_SIZE  8192
```

Macros are adequate, but not *good*. Why? Macros in general ignore scope, so you typically have to give macros *very* specific (long) names to avoid collision.

## Enumerations

Enumerations in [C](https://dev.to/pauljlucas/enumerations-in-c-ae7) and [C++](https://dev.to/pauljlucas/enumerations-in-c-pn9) are better, especially for declaring a set of *related* constants. In C++ with `enum class`

, they can even be scoped to avoid collisions; in C, however, they’s still in the global scope.

The other caveat is that they can be constants only for integral values.

## `const`

As I described for [C](https://dev.to/pauljlucas/c-const-conundrum-j2l) and [C++](https://dev.to/pauljlucas/const-conundrum-2bfj), you can use `const`

for constants, e.g.:

``` js
static unsigned const BUF_SIZE = 8192;

char BUF[ BUF_SIZE ];

int main() {
  char local_buf[ BUF_SIZE ];
  // ...
}
```

In C++, that will compile just fine without warning; in C, it’ll either be accepted with warnings or rejected entirely, especially if you disable language extensions. Why? Because `const`

is a misnomer since it really means *immutable*, not *constant*, and C is more picky about it.

While the declaration of `BUF`

might be accepted, the declaration of `local_buf`

will either be considered a variable length array (VLA) (that, as I [pointed out](https://dev.to/pauljlucas/obscure-c99-array-features-3270), you should probably never use), or rejected since VLAs are an optional feature and not all compilers support them (notably, Microsoft’s C compiler doesn’t).

A common work-around in C (prior to C23, see below) is to (ab)use `enum`

:

```
enum {
  BUF_SIZE = 8192
};
```

That is, use a nameless enumeration. The advantage is that enumeration constants really are *constant*.

## `constexpr`

If you’re using C++11 or later, or C23 or later, there’s `constexpr`

. Unlike `const`

, `constexpr`

really means *constant*. This is by far the best option for declaring constants:

```
constexpr unsigned BUF_SIZE = 8192;
```

## Conclusion

To summarize:

- If you’re declaring a set of related, integral constants, use
`enum`

(in C) or`enum class`

(in C++). - Otherwise, use
`constexpr`

if you can. - Otherwise, use
`const`

. - Otherwise, use
`#define`

as a last resort.
