条件运算符的未执行分支是否会导致编译时出现未定义的行为?

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

提问人:user16217248 提问时间:10/17/2023 最后编辑:user16217248 更新时间:10/18/2023 访问量:99

问:

据我所知,如果至少条件和执行的分支是编译时常量,则可以在编译时计算三元运算符。但是,如果其中一个未执行的参数会调用定义的行为,该怎么办?在运行时,未执行的分支不会导致未定义的行为,因为在这种情况下,C 会彻底中断?:

但是,这是否适用于编译时?

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

这是否保证会导致定义的行为?这似乎适用于 GCC。正如预期的那样,将表达式更改为编译时 false 值不会编译,因为编译器将尝试评估哪些行为是未定义的行为,无论是在编译时还是在运行时。如果我更改为其他一些无效构造,例如 *(int *)0,则行为不会改变。1/01/0

我的问题是,在编译时上下文中,未定义/无效/非编译时构造(不包括语法错误)可能出现在三元运算符的未执行分支中的这种行为不会引起问题,这是特定于 GCC 还是可移植的?

根据文档,GCC有:__builtin_constant_p()

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

即使 EXPRESSION 不是常量表达式,这也是一个可接受的初始值设定项,...

这个示例用法表明 GCC 明确允许我提出的要求,但标准 C 是否允许?__builtin_constant_p()

永远不会执行的代码可以调用未定义的行为吗?是一个不同的问题,因为问题中列出的代码位于函数的语句中。我的问题特别关于编译时值,例如全局/静态变量的初始值设定项等。ifmain()

C 语言律师 条件运算符 时编译 时间常量

评论

0赞 Andrew Henle 10/17/2023
顺便说一句,我很确定它不符合“常量表达式”的 C 标准定义,但 GCC 似乎还是接受了它:“实现可以接受其他形式的常量表达式。因此,它甚至可能不能保证一般的编译。另请注意,C23 提供了 constexpr,它在这里可能很有用。sizeof(char) == 1 ? 1 : 1/0;
1赞 Eric Postpischil 10/17/2023
@AndrewHenle:我看不出它不是一个严格符合的整数常量表达式,除了尾随。 是允许的,是允许的,是允许的,是允许的,是允许的,是允许的。;sizeof==? :.10
0赞 Some programmer dude 10/17/2023
说到UB,编译器确实被赋予了相当宽泛的空间,基本上可以做任何它想做的事情。此外,虽然编译器可能能够检测到 UB(通常有很多警告表明可能存在 UB),但要使 UB 实际表现出来,它需要在运行时执行。
0赞 dbush 10/17/2023
类似但不是严格意义上的傻瓜:stackoverflow.com/questions/58661493/......
0赞 user16217248 10/17/2023
@dbush 我的问题不是另一个问题的重复。我的明确是关于编译时上下文的。

答:

4赞 Eric Postpischil 10/17/2023 #1

该行为完全由 C 标准定义,无论编译器何时选择计算表达式。

不计算条件运算符的未选择操作数 (C 2018 6.5.15 4)。C 标准指定程序的行为是不计算操作数,无论条件表达式是在编译时还是在运行时实际计算,都是如此。

除以零等运算只有在计算时才具有未定义的行为。

对于一般操作,6.5 5 表示“如果在表达式计算期间发生异常情况(即,如果结果未在数学上定义或不在其类型的可表示值范围内),则该行为是未定义的。因此,这些行为只能在“表达式评估期间”出现。除以零有点不同,因为 6.5.5 5 表示除以零具有未定义的行为,与 6.5 5 分开。但是,这是在语义子句中,它告诉我们计算表达式的行为是什么。