当前位置: 首页 > 知识库问答 >
问题:

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

齐才艺
2023-03-14

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

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

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

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

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

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

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

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

共有3个答案

萧懿轩
2023-03-14

在其他编程语言中也有过这样的例子:以前未定义的行为在后来的标准中被定义。我能记住的一个例子是在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将未指定。它要么是未定义的,要么是定义明确的。

季博
2023-03-14

现代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;这是一种未定义的行为,我们不能对此做任何假设。可以以任何顺序执行一个、两个或不执行任何功能。程序可能会崩溃,产生不正确的结果,产生看似正确的结果,或者什么都不做。

  • 袁英豪
    2023-03-14

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

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

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

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

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

    (C2011,3.4.3)

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

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

    (C2011,3.4.4)

    你说过

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

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

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

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

    当你假设

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

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

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

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

    这还不完全清楚。

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

     类似资料:
    • 我一直在温习我未定义的行为规则,并阅读了以下内容: 未定义的行为和序列点 为什么f(i=-1,i=-1)行为未定义 为什么`x-- 在C 11中,“i = i 1”是否表现出未定义的行为? 最后有三个问题: < li >形式为< code>i=i 的术语的未定义行为规则是否适用于非整型?(表达式应翻译为< code > I . operator(I . operator(I)),由于每个函数调用都

    • 问题内容: 也许对编译器进行优化后并不重要,但是在C / C ++中,我看到大多数人以以下形式进行for循环: 使用postfix 完成增量操作。我得到了两种形式之间的区别。i 返回i的当前值,但是在安静时将1加到i。++ i首先将1加到i,然后返回新值(比i大1)。 我认为i 需要做更多的工作,因为除了下一个值外,还需要存储一个前一个值:推 (&i)进行堆栈(或加载至寄存器);递增(&i)。与

    • 好吧,所以那里没有UB。现在我的问题是,如果将赋值运算符从更改为(或类似运算符),会发生什么。 表达式的求值是否导致未定义的行为? 在我看来,这个标准在这里似乎自相矛盾。由于的LHS仍然是一个L值(其RHS仍然是一个prvalue),就(1)和(2)而言,同样的推理也适用;在上的操作数的计算中没有未定义的行为。对于(3),复合赋值的操作(更准确地说是该操作的副作用;如果需要,它的值计算在任何情况下

    • 我碰巧知道,在下面的表达式中,使用将导致无限流,将始终为0。我之所以困惑是因为我认为返回的值没有被使用,即便如此,它也不应该中断之后的增量。

    • 问题内容: 以下Java程序平均需要0.50秒至0.55秒的时间运行: 如果我替换为,则需要0.60到0.65秒的时间来运行。怎么会? 我对该程序的每个版本运行了15次,两次交替运行。结果如下: 最快的运行时间比最慢的运行时间长。如果它们具有相同的效率,则发生这种情况的可能性将小于。 问题答案: 字节码的顺序略有不同。 : vs : 乍看之下,这没有什么区别;如果有的话,第二个版本更理想,因为它减

    • 考虑以下C程序: null 访问易失性对象、修改对象、修改文件,或者调用执行那些操作中的任何操作的函数都是副作用,它们是执行环境状态的改变。表达式的计算通常包括值计算和副作用的启动。用于lvalue表达式的值计算包括确定指定对象的标识。 Sequenced before是单线程执行的计算之间的非对称、传递、成对关系,它导致这些计算之间的部分顺序。给定任意两个评价A和B,如果A排序在B之前,那么A的

    • 本文向大家介绍为什么“ while(i ++ 相关面试题,主要包含被问及为什么“ while(i ++ 时的应答技巧和注意事项,需要的朋友参考一下 正如其他人指出的那样,该测试在许多方面都有缺陷。 你没有告诉我们到底 如何 ,你做了这个测试。但是,我试图实施这样的“幼稚”测试(无冒犯): 当使用默认设置运行它时,似乎有很小的差异。但是,当您使用该标志运行基准测试时,基准测试的 真正 缺陷就变得显而

    • 如果标量对象上的副作用相对于同一标量对象上的另一个副作用或使用同一标量对象的值进行的值计算没有排序,则行为是未定义的。 缺陷报告中的解释没有帮助我理解。lvalue-to-rvalue转换与任何事情有什么关系?我错在哪里了?