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

是什么使我=我1;在C17中合法吗?

刁英朗
2023-03-14

在您开始叫喊未定义的行为之前,N4659(C 17)中明确列出了这一点

  i = i++ + 1;        // the value of i is incremented

然而在N3337(C 11)

  i = i++ + 1;        // the behavior is undefined

什么改变了?

从[N4659 basic.exec]中收集到的信息

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的求值是不排序的。[...] 运算符操作数的值计算在运算符结果的值计算之前排序。如果一个内存位置上的副作用相对于同一内存位置上的另一个副作用或使用同一内存位置中任何对象的值进行的值计算未排序,并且它们可能不是并发的,则该行为是未定义的。

其中值在[N4659基本类型]中定义

对于普通的可复制类型,值表示是对象表示中确定值的一组位,该值是实现定义的值集的一个离散元素

来自[N3337 basic.exec]

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的求值是不排序的。[...] 运算符操作数的值计算在运算符结果的值计算之前排序。如果标量对象上的副作用相对于同一标量对象上的另一副作用或使用同一标量对象的值进行的值计算未排序,则该行为未定义。

同样,值定义在[N3337basic.type]

对于普通的可复制类型,值表示是对象表示中确定值的一组位,该值是实现定义的值集的一个离散元素。

它们是相同的,只是提到并发性并不重要,而且使用内存位置而不是标量对象,其中

算术类型、枚举类型、指针类型、指向成员类型的指针、std::nullptr\u t,以及这些类型的cv限定版本统称为标量类型。

这不会影响示例。

来自[N4659 expr.ass]

赋值运算符(=)和复合赋值运算符都从右向左分组。它们都需要一个可修改的左值作为左操作数,并返回一个引用左操作数的左值。如果左操作数是位字段,则所有情况下的结果都是位字段。在所有情况下,赋值都是在右操作数和左操作数的值计算之后、赋值表达式的值计算之前排序的。右操作数在左操作数之前排序。

自[N3337 expr.ass]

赋值运算符(=)和复合赋值运算符都从右向左分组。它们都需要一个可修改的左值作为左操作数,并返回一个引用左操作数的左值。如果左操作数是位字段,则所有情况下的结果都是位字段。在所有情况下,赋值都是在右操作数和左操作数的值计算之后、赋值表达式的值计算之前排序的。

唯一的区别是N3337中没有最后一句话。

但是,最后一句话不应该有任何重要性,因为左操作数i既不是“另一个副作用”,也不是“使用同一标量对象的值”,因为id表达式是左值。

共有3个答案

纪俊良
2023-03-14

在旧的C标准和C11中,赋值运算符文本的定义以以下文本结尾:

操作数的求值是不排序的。

这意味着操作数中的副作用是无序的,因此如果使用相同的变量,它们的行为肯定是未定义的。

这段文字在C11中被简单地删除了,让它有些模糊。是UB还是不是?这一点已在C 17中得到澄清,他们补充说:

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

作为旁注,在更旧的标准中,这一切都非常清楚,例如C99:

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

基本上,在C11/C11中,当他们删除这段文字时,他们搞砸了。

谭新知
2023-03-14

你确定了新判决

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

您正确地确定了左操作数作为左值的求值是不相关的。但是,sequenced before被指定为传递关系。因此,完整的右操作数(包括后增量)也会在赋值之前排序。在C 11中,赋值前只对右操作数的值计算进行排序。

邹祺然
2023-03-14

在C 11中,“赋值”的行为,即修改LHS的副作用,在右操作数的值计算之后排序。注意,这是一个相对“弱”的保证:它只产生与RHS的值计算相关的排序。它没有说明RHS中可能存在的副作用,因为副作用的发生不是价值计算的一部分。C 11的要求没有确定分配行为与RHS的任何副作用之间的相对顺序。这就是创造UB潜力的原因。

在这种情况下,唯一的希望是RHS中使用的特定运营商做出的任何额外保证。如果RHS使用前缀,则特定于前缀形式的排序属性将在本例中保存日期。但是postfix则是另一回事:它没有做出这样的保证。在C 11中,=和postfix的副作用在本例中彼此之间没有顺序。这就是UB。

在C 17中,赋值运算符规范中增加了一句额外的语句:

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

结合以上内容,这是一个非常有力的保证。它将RHS中发生的一切(包括任何副作用)排序在LHS中发生的一切之前。由于实际分配是在LHS(和RHS)之后进行排序的,因此额外的排序将分配行为与RHS中存在的任何副作用完全隔离。这种更强的排序消除了上述UB。

(更新时考虑了@John Bollinger的评论。)

 类似资料:
  • 介绍 你好,我是 Franklin Risby 教授,很高兴认识你。接下来我们将共度一段时光了,因为我要教你一些函数式编程的知识。好了,关于我就介绍到这里,你怎么样?我希望你已经熟悉 JavaScript 语言了,关于面向对象也有一点点的经验了,而且自认为是一个合格的程序员。希望你没有昆虫学博士学位也能找到并杀死一些臭虫(bug)。 我并不假设你之前有任何函数式编程相关的知识——我们都知道假设的后

  • 我理解mockito.verify()用于确保使用所需参数调用模拟方法。但我不明白这其中的用意。我经常看到类似这样的测试: 测试验证在调用UserService的createUser方法时是否调用了UserDAO的create方法。看起来很荒唐。如果我改变了UserService的实现,使它不调用UserDAO的方法,那么即使实现是正确的,测试也会失败。 我承认在某些情况下,可能需要验证方法被调用

  • 问题内容: 我有这个web.xml 突出显示,IDE给出的错误是:“发现无效的内容,从element开始… 我还需要做什么? 问题答案: 使用以下表示法: 但我建议阅读此链接。本教程将使您了解在JSP 2.0的情况下如何避免在web.xml中声明标签库。

  • 问题内容: 为什么我们在Java中使用该方法?(请给出有关内存限制的答案。)这会减少内存使用吗?如果是,那怎么办?这样会减少内存泄漏的影响吗? 问题答案: 除了 不使用克隆外,还实现一个复制构造函数 ,您询问了内存限制。 克隆 的想法是创建克隆对象的精确副本。因此,在最坏的情况下,此后您将使用两倍的内存量。实际上- 少了一点,因为String经常被嵌入并且(通常)不会被克隆。即使由clone方法/

  • 本文向大家介绍为什么我们在jQuery中使用JSON.stringify()方法?,包括了为什么我们在jQuery中使用JSON.stringify()方法?的使用技巧和注意事项,需要的朋友参考一下 JSON或JavaScript Object Notation是一种轻量级的基于文本的开放标准,旨在用于人类可读的数据交换。使用JSON.stringify()方法将JavaScript对象转换为字符

  • 目前,我正在尝试在这里实现汽车示例的变体: https://www.elastic.co/blog/managing-relations-inside-elasticsearch 如果我运行: 代码工作正常。 但是如果我删除索引并将2015从字符串更改为数字: 我收到以下错误消息: {“error”:{“root\u-cause”:[{“type”:“非法\u-argument\u-excepti