提问者:小点点

如果“i = i”不被认为是未定义的行为,会发生什么?[已关闭]


< b >想改进这个问题?更新问题,以便通过编辑此帖子用事实和引用来回答问题。

我很难理解未指定和未定义行为之间的区别。我认为试着理解一些例子是有用的。例如,< code>x = x 。这项任务的问题在于:

在上一个和下一个序列点之间,对象应通过表达式的计算最多修改一次其存储值。此外,应仅读取先前的值以确定要存储的值。

这违反了应规则,但没有显式调用未定义的行为,但它涉及UB根据:

操作数的求值顺序未指定。如果试图修改赋值操作符的结果或在下一个序列点之后访问它,则行为是未定义的。

假设这些规则都不存在,并且没有其他规则使“< code>x = x ”无效。那么< code>x的值将是未指定的,对吗?

之所以产生疑问,是因为有时有人认为C中的东西在“默认情况下”是UB的,这只是有效的,你可以证明构造是有效的。

编辑:正如P.W .所指出的,对于C,这个问题有一个有点相关的、广受欢迎的版本:是什么使I = I 1;在C 17合法吗?。


共3个答案

匿名用户

我很难理解未指定行为和未定义行为之间的区别。

那么让我们从标准中这些术语的定义开始:

未定义的行为行为,在使用不可移植或错误的程序结构或错误的数据时,本国际标准对此没有要求

注:可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特有的记录方式表现(发出或不发出诊断消息),到终止翻译或执行(发出诊断消息)。

示例未定义行为的一个示例是整数溢出行为。

(C2011,3.4.3)

未指定的行为使用未指定的值,或本国际标准提供两种或两种以上可能性的其他行为,并且在任何情况下对选择哪种行为没有进一步的要求

示例 未指定行为的一个示例是计算函数参数的顺序。

(C2011,3.4.4)

你说过

之所以产生疑问,是因为有时有人认为C中的东西在“默认情况下”是UB的,这只是有效的,你可以证明构造是有效的。

把它称为一个论点,似乎对它的有效性有一些怀疑,这也许是过分夸大了这一点。事实上,它反映了标准的明确语言:

如果违反了出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义。未定义的行为在本标准中以“未定义的行为”一词或省略任何明确的行为定义来表示。这三者在侧重点上没有区别;它们都描述了“未定义的行为”。

(C2011,4/2;着重部分由作者添加)

当你假设

假设这些规则都不存在,并且没有其他规则使x = x“无效”。

,这不一定会改变任何事情。特别是,删除操作数的计算顺序未指定的显式规则不会指定顺序。我倾向于争辩说顺序仍然未指定,但另一种选择是行为是未定义的。明确表示它未指定的主要目的是回避这个问题。

当一个对象在两个序列点之间被修改两次时,显式声明UB的规则就不那么清楚了,但还是一样。有人可能会说,该标准仍然没有为您的示例案例定义行为,使其没有定义。我认为这有点牵强,但这正是为什么有一个明确的规则是有用的,无论如何。为您的情况定义行为是可能的——例如Java但是由于各种技术和历史原因,C选择不这样做。

那么x的值将是未指定的,对吗?

这还不完全清楚。

还请理解,该标准的各种规定在很大程度上并不是孤立的。它们旨在作为一个(大部分)连贯的整体协同工作。删除或更改随机规定有相当大的风险产生不一致或差距,从而难以对结果进行推理。

匿名用户

现代C11/C17改变了文本,但它的含义几乎相同。C17 6.5/2:

如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值的值计算是未排序的,则行为是未定义的。

这里有几个略有不同的问题,混合成一个:

>

  • 在序列点之间,x多次写入(副作用)。根据上述,这是UB。
  • 在序列点之间,表达式至少包含一个副作用,并且同一个变量的值计算与要存储的值无关。根据上述,这也是UB。
  • 在表达式x=x中,操作数x的求值没有相对于操作数x进行排序。根据C17 6.5.16,求值顺序是未指定的行为。

    更新左操作数的存储值的副作用在左操作数和右操作数的值计算之后排序。操作数的计算是无序的。

    如果不是第一个引用的部分标记了这个UB,那么我们仍然不知道< code>x 是在左< code>x操作数的求值之前还是之后排序,所以很难推断这怎么会变成“只是未指定的行为”。

    C 17实际上修复了这部分,使其在那里定义良好,不像在C或更早的C版本中。他们通过定义序列顺序来做到这一点(C 17 8.5.18):

    在所有情况下,赋值都在右操作数和左操作数的值计算之后以及赋值表达式的值计算之前进行排序。右操作数在左操作数之前排序。

    我看不出这里怎么会有任何中间立场;表达式要么未定义,要么定义良好。

    未指定行为是我们无法知道或假设的确定性行为。但与未定义的行为不同,它不会导致崩溃和随机程序行为。一个很好的例子是A()b()。我们不知道哪个函数将首先执行-如果同一行稍后出现在同一程序中,程序甚至不必保持一致。但我们可以知道,这两个函数都将执行,一个在另一个之前。

    不像< code > x = a()b()x;这是一种未定义的行为,我们不能对此做任何假设。可以以任何顺序执行一个、两个或不执行任何功能。程序可能会崩溃,产生不正确的结果,产生看似正确的结果,或者什么都不做。

  • 匿名用户

    在其他编程语言中也有过这样的例子:以前未定义的行为在后来的标准中被定义。我能记住的一个例子是在C中,在C 11中未定义的行为在C 17中得到了很好的定义。

    i = i++ + 1; // the behavior is undefined in C++11 
    
    i = i++ + 1; // the behavior is well-defined in C++17. The value of i is incremented
    

    关于这个话题,有一个广受欢迎的问题。使这一定义明确的是C 17标准中的一项保证:

    右操作数在左操作数之前排序。

    因此,从某种意义上说,标准委员会的人有责任改变标准,并提供强有力的保证,使其得到良好的定义。

    但我不认为像x=x将未指定。它要么是未定义的,要么是定义明确的。