鉴于以下计划:
#include <stdio.h>
int main(void)
{
int i = 1, j = 2;
int val = (++i > ++j) ? ++i : ++j;
printf("%d\n", val); // prints 4
return 0;
}
val
的初始化似乎可能隐藏了一些未定义的行为,但我没有看到任何一个对象被修改多次或修改并使用它们之间没有序列点的任何点。有人可以纠正或证实我吗?
此代码的行为已明确定义。
保证条件中的第一个表达式在第二个表达式或第三个表达式之前进行求值,并且只评估第二个或第三个表达式中的一个。这在C standard的第6.5.15p4节中描述:
第一个操作数被评估;在其评估与第二或第三操作数的评估之间存在一个序列点(以评估者为准)。仅当第一个操作数不等于0时才评估第二个操作数;仅当第一个操作数比较等于0时才评估第三个操作数;结果是第二个或第三个操作数的值(无论哪个被评估),转换为下面描述的类型。
在你的表达的情况下:
int val = (++i > ++j) ? ++i : ++j;
首先评估++i > ++j
。 i
和j
的递增值用于比较,因此它变为2 > 3
。结果是错误的,所以然后评估++j
而不是++i
。因此,j
(即4)的(再次)递增值被赋予val
。
太晚了,但也许有用。
(++i > ++j) ? ++i : ++j;
在文档ISO/IEC 9899:201xAnnex C(informative)Sequence points
中,我们发现有一个序列点
在条件α:运算符的第一个操作数的评估与第二个和第三个操作数中的任何一个之间进行评估
为了明确定义行为,不得在2个序列点之间修改2次(通过副作用)相同的对象。
在你的表达中,可能出现的唯一冲突将在第一和第二个++i
或++j
之间。
在每个序列点,最后存储在对象中的值应与抽象机器规定的值一致(这是您在纸上计算的,就像在图灵机上一样)。
引自5.1.2.3p3 Program execution
在表达式A和B的评估之间存在序列点意味着在与B相关联的每个值计算和副作用之前,对与A相关联的每个值计算和副作用进行排序。
当您的代码中有副作用时,它们会按不同的表达式排序。规则说,在2个序列点之间,您可以根据需要置换这些表达式。
例如。 i = i++
。因为此表达式中涉及的运算符都不代表序列点,所以您可以根据需要置换副作用的表达式。 C语言允许您使用任何这些序列
i = i; i = i+1;
或i = i+1; i=i;
或tmp=i; i = i+1 ; i = tmp;
或tmp=i; i = tmp; i = i+1;
或提供与abstract semantics of computation相同结果的任何内容都要求对此计算进行解释。标准ISO9899将C语言定义为抽象语义。
您的程序中可能没有UB,但问题是:语句int val = (++i > ++j) ? ++i : ++j;
是否调用未定义的行为?
答案是肯定的。增量操作中的任何一个或两个都可能溢出,因为i
和j
是有符号的,在这种情况下所有的投注都是关闭的。
当然,这不会在您的完整示例中发生,因为您已将值指定为小整数。
我打算评论@Doug Currie,签名整数溢出是一个太过分的小问题,虽然在技术上是正确的答案。反之!
再想一想,我认为道格的回答不仅是正确的,而且假设一个不完全琐碎的三线,如例子(但是一个可能有一个循环或类似的程序)应该扩展到一个明确的,明确的“是”。原因如下:
编译器看到int i = 1, j = 2;
,因此它知道++ i将等于j
,因此不可能大于j
甚至++j
。现代优化者看到了这些微不足道的东西。
当然,除非其中一个溢出。但优化器知道这将是UB,因此假定,并根据优化,它永远不会发生。
所以三元运算符的条件总是为假(在这个简单的例子当然,但即使在循环中重复调用也是如此!),并且i
只会递增一次,而j
将总是递增两次。因此,不仅j
总是大于i
,它甚至在每次迭代时都会获得(直到溢出发生,但这根据我们的假设不会发生)。
因此,允许优化器无条件地将其转换为++i; j += 2;
,这肯定不是人们所期望的。
这同样适用于例如具有未知值i
和j
的循环,例如用户提供的输入。优化器可能很好地认识到操作序列仅取决于i
和j
的初始值。因此,可以通过复制循环来优化随后的条件移动的增量序列,对于每种情况一次,并且使用单个if(i>j)
在两者之间切换。然后,当我们在它的时候,它可能会将重复增量的循环折叠成像(j-i)<<1
这样的东西。或者其他的东西。
假设溢出永远不会发生 - 这是假设优化器被允许制造,并且确实做出 - 这样的修改可能完全改变程序的整个意义和操作模式是完全正确的。
尝试并调试它。