Search code examples
clanguage-lawyerconditional-operatorcompile-timecompile-time-constant

Can unexecuted branches of a conditional operator cause undefined behavior at compile-time?


As far as I can tell, the ternary ?: operator can be evaluated at compile time if at least the condition and the executed branch are compile-time constants. However, what if one of the unexecuted arguments would invoke undefined behavior? At runtime, unexecuted branches cannot cause undefined behavior, because in that case, C would break thoroughly.

However does this apply to compile time?

static const int GLOBAL_VARIABLE = sizeof(char) == 1 ? 1 : 1/0;
// sizeof(char) is guaranteed to be 1 so the 1/0 is never evaluated

Is this guaranteed to cause defined behavior? This appears to work on GCC. As expected changing the expression to compile-time false value does not compile since the compiler will try to evaluate 1/0 which is undefined behavior regardless whether at compile or runtime. The behavior does not change if I change 1/0 to some other invalid construct such as *(int *)0.

My question is, this behavior that undefined/invalid/non-compile-time constructs (excluding syntax errors) can appear in the unexecuted branch of a ternary operator in a compile-time context will not cause problems, is that specific to GCC or portable?

According to the docs GCC has __builtin_constant_p():

static const int table[] = {
  __builtin_constant_p (EXPRESSION) ? (EXPRESSION) : -1,
  /* … */
};

This is an acceptable initializer even if EXPRESSION is not a constant expression, ...

This example usage of __builtin_constant_p() suggests that GCC explicitly allows what I ask, but does standard C allow it?

Can code that will never be executed invoke undefined behavior? is a different question because the code listed in the question is inside an if statement in the main() function. My question is specifically about compile time values such as initializers to global/static variables, etc.


Solution

  • The behavior is fully defined by the C standard, regardless of when the compiler chooses to evaluate the expression.

    The unselected operand of the conditional operator is not evaluated (C 2018 6.5.15 4). The C standard specifies the behavior of the program is that the operand is not evaluated, and this is true regardless of whether the conditional expression is actually evaluated at compile time or run time.

    Operations such as division by zero have undefined behavior only if they are evaluated.

    For operations generally, 6.5 5 says “If an exceptional condition occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.” So these sorts of behaviors can arise only “during the evaluation of an expression.” Division by zero is a bit different because 6.5.5 5 says division by zero has undefined behavior, separately from 6.5 5. However, this is in a semantics clause that tells us what the behavior of evaluating the expression is.