clanguage-lawyerc11constant-expressionc17

Is there a standard way to guarantee that a certain (constant) expressions will be evaluated at compile (translation) time?


I am quite surprised that C does not guarantee that certain (constant) expressions are evaluated at compile (translation) time.

C11 (6.6 Constant expressions) (emphasis added):

A constant expression can be evaluated during translation rather than runtime, ...

Hence, two questions:

  1. Is there a standard way to guarantee that a certain (constant) expressions will be evaluated at compile (translation) time?
  2. Extra question: why C does not guarantee it? What were the (technical) obstacles to guarantee it?

UPD20230711. In _Static_assert declaration an integer constant expression is required to be evaluated at translation time, because per Semantics section (C11, 6.7.10p3) an implementation checks whether the constraint is violated. If the constraint is violated, then the implementation is required to produce a diagnostic message. If the diagnostic message is produced at translation time, then the integer constant expression is evaluated at translation time. This means that in some contexts a constant expression can (i.e. not required) be evaluated at compile (translation) time (e.g. switch(<const_expr>), in other contexts a constant expression shall (i.e. required) be evaluated at compile (translation) time (e.g. _Static_assert (<const_expr>, "");.


Solution

  • The C standard in general is unhelpful in describing what may go on at compile-time and run-time respectively. The whole language is based on the (rather unhelpful) concept of an "abstract machine" in C17 5.1.2.3

    The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.
    ...
    Evaluation of an expression in general includes both value computations and initiation of side effects...
    In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced...

    The above includes the formal definition of evaluation, which is also rather unhelpful. Basically the implementation is free to do anything in the ways of optimizations as long as side effects and "the observable behavior" are respected. There are no requirements placed stating that the compiler must perform certain optimizations. It's possible to build a compiler with no optimizations at all and it can still be a conforming implementation.

    As for 6.6 and constant expression, the intention is surely to state the rules for what makes valid compile-time constants. In case integer constant expressions aren't evaluated at compile-time, then it becomes very impractical to generate an executable, to the point where the whole language would collapse. Indirectly, there are many requirements that can't be fulfilled unless constant expressions are evaluated at compile-time.

    For example, if array sizes of static storage duration objects weren't known at compile-time, then how would you generate the start-up code for initializing them? It must occur before main() is called - this is actually guaranteed by 5.1.2:

    All objects with static storage duration shall be initialized (set to their initial values) before program startup.

    And if main contains static int arr [5] = {1,2,3,4,5}; then this object must already be initialized when we reach the declaration. So we can only fulfil 5.1.2 if the integer constant 5 in my example, an integer constant expression, is in fact evaluated at compile-time.