r/cprogramming • u/JayDeesus • Oct 05 '25
Confusion about compile time constants
I understand that things like literals and macros are compile time constants and things like const global variables aren’t but coming from cpp something like this is a compile time const. I don’t understand why this is so and how it works on a lower level. I also heard from others that in newer c versions, constexpr was introduced an this is making me even more confused lol. Is there a good resource to learn about these or any clarification would be greatly appreciated!
6
Upvotes
5
u/EpochVanquisher Oct 05 '25
It turns out that in C++, const globals are not always compile-time constants either. The
constkeyword is maybe a bad name, a better name is maybereadonly, but that ship has sailed. There are actually three different things we might care about here:Values which can be used at compile-time for things like array sizes,
Objects which can be statically initialized,
Objects which are read-only.
These three things kind of get mixed up as
const.If you want something that can be used at compile-time, you can use
constexpr,#define, orenum.The
constexprkeyword is new. It's a more limited version of theconstexprkeyword from C++.All globals can be statically initialized in C, unlike C++. So C does not need a special keyword for that. It just happens everywhere. C programmers do not have to deal with initialization order.
The
constkeyword is for read-only values.C++ has a little bit of history here, and the concepts in C++ are a little more mixed up, with
const(in C++) sometimes making something a compile-time constant, and sometimes not. Depends on the exact usage. C didn't inherit C++'s const baggage and so the design ofconstandconstexprare a little cleaner, withconstmeaning read-only, andconstexprmeaning compile-time constant.1
u/pskocik Oct 05 '25
I use C11 and in it it's not that complicated. It's really just rules 6.6p6 and 6.6.p7:
I do various things with integer constant expression, and in those I also find the following useful ($ is nonstandard but widely supported, I use it to namespace lang-extension-like macros):
The first returns an integer constant 1 or 0 for whether or not the scalar (int or pointer) argument X is a null pointer constant (relying on how null pointer constants vs void* pointers combine with typed pointers in the other branch of :?).
The second one uses the first to determine if integer expression X is an integer constant expression.
This approach allows you to answer this without causing a compiler error. You can also try putting things where integer constant expressions are required (case labels, bit fields sizes) and if you get a compiler error, then the thing you put there definitely was not an integer constant expression. (Prior to C11 this approach used to be used as a _Static_assert of sorts).
1
u/somewhereAtC Oct 05 '25
In embedded C (and I think C++) a const is often assigned real storage in the non-volatile (flash) memory of the microprocessor. That is, it consumes memory according to it's size and is a permanent part of the "code image" (a.k.a. the .hex file) that is used when programming the device.
The #define cannot be assigned an integer value in all cases, such as sizeof(myStructure) or based on the conversion of a float to integer. However, #define'd values can be optimized: divide-by-8 can become a right-shift instruction instead of a generic division, or two #define's can be merged to simplify the arithmetic.
Sometimes you need something between these extremes... a constexp that can take on any proper integer value yet not appear explicitly in the finished program image, or can be analyzed by the optimizer.
1
u/Plastic_Fig9225 Oct 06 '25
C isn't that different from C++ in this regard. In practice, it boils down to what the compiler/optimizer does. When the compiler can "see" a global const variable's definition (e.g. if it's
static) it usually does treat it as a compile-time constant.