语句'int val =(++ i> ++ j)吗? ++ i:++ j;`调用未定义的行为?

时间:2019-03-14 19:23:08

标签: c ternary-operator sequence-points

给出以下程序:

#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的初始化似乎可能隐藏了一些未定义的行为,但是我看不到对象被多次修改或在其间没有序列点的情况下被修改和使用的任何地方。有人可以吗 纠正还是证实我的观点?

4 个答案:

答案 0 :(得分:37)

此代码的行为已明确定义。

保证条件表达式中的第一个表达式先于第二个表达式或第三个表达式求值,并且仅第二个或第三个表达式之一求值。 C standard的6.5.15p4节对此进行了描述:

  

第一个操作数被求值;有一个顺序点   在其评估与第二或第三次评估之间   操作数(无论哪个被评估)。第二个操作数被求值   仅当第一个比较不等于0时;第三个操作数是   仅当第一个比较等于0时才求值;结果是   第二个或第三个操作数的值(以   评估),转换为以下所述的类型。

对于您的表情:

int val = (++i > ++j) ? ++i : ++j;

++i > ++j首先被评估。比较中使用ij的增量值,因此它变成2 > 3。结果为假,因此对++j进行评估,而对++i进行评估。因此,j(即4)的(再次)递增值然后被分配给val

答案 1 :(得分:8)

为时已晚,但可能有用。

(++i > ++j) ? ++i : ++j;

在文档ISO/IEC 9899:201xAnnex C(informative)Sequence points中,我们发现有一个序列点

  

在条件?:运算符的第一个操作数的求值与第二个和第三个操作数中的任一个的求值之间

为了获得良好定义的行为,一个对象不得在2个序列点之间修改同一对象2次(通过副作用)。

在您的表情中,唯一可能出现的冲突是在第一个++i和第二个++j之间。

在每个序列点上,对象中最后存储的值应与抽象机所规定的值一致(这是您在纸上计算的结果,例如在图灵机上)。

引用5.1.2.3p3 Program execution

  

在表达式A和B的求值之间存在一个序列点,这意味着与A关联的每个值计算和副作用都要在与B关联的每个值计算和副作用之前进行排序。

当代码中有副作用时,它们将按不同的表达式排序。该规则说,您可以在两个序列点之间随意替换这些表达式。

例如。 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语言定义为抽象语义。

答案 2 :(得分:5)

您的程序中可能没有UB,但存在以下问题:     语句int val = (++i > ++j) ? ++i : ++j;是否调用未定义的行为?

答案是肯定的。由于ij已签名,因此两个或两个增量操作都可能溢出,在这种情况下,所有投注均关闭。

当然,在您的完整示例中不会发生这种情况,因为您已将值指定为小整数。

答案 3 :(得分:0)

我要评论@Doug Currie,尽管技术上正确作为答案,但有符号整数溢出实在是太牵强了。相反!

再考虑一遍,我认为道格的答案不仅是正确的,而且还假设示例中的三层线并不完全是琐碎的(但可能包含循环等程序)应扩展为清晰,明确的“是”。原因如下:

编译器看到int i = 1, j = 2;,因此它知道 ++ i将等于j,因此不可能大于j甚至甚至不能大于{ {1}}。现代优化器会看到如此琐碎的事情。

当然,除非其中之一溢出。但是优化器知道这将是UB,因此假设并根据它永远不会发生进行优化。

因此三元运算符的条件始终为false(当然,在这个简单示例中,但是即使在循环中重复调用也是如此!),并且++j仅会递增 ,而i将始终两次递增。因此,不仅j总是比j大,而且甚至在每次迭代时都获得收益(直到发生溢出,但根据我们的假设,这从未发生)。

因此,允许优化器无条件将其转换为i,这肯定不是人们期望的。

同样适用于具有++i; j += 2;i未知值的循环,例如用户提供的输入。优化器可能会很好地认识到,操作顺序仅取决于ji的初始值。因此,可以通过以下方式来优化递增顺序,然后进行条件移动:复制循环,每种情况一次,并使用单个j在两者之间切换。然后,当我们使用它时,它可能会将重复的按2递增的循环折叠成刚添加的if(i>j)之类的东西。等等
在永远不会发生溢出的假设下(即允许优化器进行 make的假设),这样的修改可能会完全改变程序的整体含义和运行方式很好。

尝试并调​​试它。