为什么“ i ++ +1”本身未定义?如何确保后缀的副作用在计算+之后发生?

我知道这个问题经常在其版本“ i = i ++ +1”中被问到,但是我出现两次,但是我的问题有所不同,那就是只针对该表达式的右手边,其定义是对我来说并不明显。我只指的是:

i++ + 1;

cppreference.com指出here

  

2)对任何运算符的操作数的值计算(而不是副作用)在运算符结果的值计算(而不是其副作用)之前进行排序。

我理解这意味着值计算是有序的,但没有对副作用进行任何陈述。

  

[...]

     

4)内置后递增和后递减运算符的值计算先于其副作用进行排序。

但是,它没有指定左操作数(在这种情况下)的副作用是相对于表达式的值计算来排序的。

它进一步指出:

  

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

这里不是这种情况吗?相对于使用相同i的加法运算符的值计算,后置运算符对i的副作用没有顺序。

为什么通常不说这个表达式是不确定的?

是因为认为加法运算符会产生一个函数调用,为此需要提供更严格的顺序保证?

huaihuai025 回答:为什么“ i ++ +1”本身未定义?如何确保后缀的副作用在计算+之后发生?

  

什么可以确保后缀的副作用在计算+之后发生?

没有这样的保证。后缀的副作用可能发生在+的值计算之前或之后。

  

相对于使用相同的i 的加法运算符的值计算,后置运算符对i的副作用没有顺序。

否,加法运算符的值计算使用其操作数的值计算结果。 +的操作数是i++(不是i)和1。正如您在问题中所涵盖的,i的读取是在i++的值计算之前进行排序的,因此(传递性)在{{1 }}。

保证按以下顺序发生以下事情:

  1. 读取+
  2. i的值计算(操作数:步骤1的结果)
  3. ++的值计算(操作步骤2和+的结果)

1的副作用必须在第1步之后发生,但可能会达到该限制。

,

i++ + 1并非由于使用后缀运算符而未定义,因为它仅对一个对象产生一种副作用,并且该对象的值仅在该位置引用。 i++表达式明确产生i的先验值,并且该值是加到1的值,而不管i实际何时更新。

(我们不知道i++ + 1的定义是否正确,因为由于各种其他原因,事情可能出错:i未初始化或不确定或无效,或者出现数字溢出或指针溢出犯)。

如果在同一评估阶段中我们尝试两次修改同一对象i++ + i++,则会发生未定义的行为。这可以用指针进行卷积,因为(*p)++ + (*q)++仅在pq指向同一位置时才递增同一对象;否则很好。

如果在同一评估阶段中,我们尝试观察在表达式中其他地方(例如i++ + i)修改的对象的值,则也会发生未定义的行为。 +的右侧访问i,但是在左侧i++的副作用方面没有顺序; +运算符不施加序列点。不用说,在i++ + 1中,1不会尝试访问i

,

在评估i++ + 1时会发生以下情况:

  • 计算子表达式i++。它产生i的先前值。
  • 评估i++还具有增加i的存储值的副作用-但请注意,未使用该增加的值。
  • 对子表达式1进行评估,得出明显的值。
  • +运算符求值,得出i++的结果加上1的结果。只有在确定了左右子表达式的值之后,这种情况才会发生(但可以在副作用发生之前或之后持续发生)。

仅保证++运算符的副作用在下一个序列点之前的某个时间发生。 (这就是C99的术语。C11标准以不同的方式提出了相同的规则。)但是由于表达式中的其他内容均不取决于该副作用,因此它何时出现并不重要。没有冲突,所以没有未定义的行为。

i++ + i中,根据副作用是否发生,在RHS上对i的评估将产生不同的结果。而且由于顺序是不确定的,因此标准举手表示行为是不确定的。但是在i++ + i中,不会发生此问题。

,

“什么可以确保后缀的副作用在计算+之后发生?”

没有什么可以保证的。您必须像使用i的原始值一样进行操作,并且在某些时候它需要执行副作用,但是只要一切运行正常,编译器如何实现这一点就无关紧要或以什么顺序。它可以(并且在某些情况下可以)将其实现为大致等同于以下任何一种情况:

auto tmp = i;
i = tmp + 1; // Could be done here,or after the next expression,doesn't matter since i isn't read again 
tmp + 1;  // produces actual value of i++ + 1

auto tmp = i + 1;
i = tmp; // Could be done here,doesn't matter since tmp isn't changed again
(tmp - 1) + 1; // produces actual value of i++ + 1

or(对于具有足够信息的原语或内联运算符重载)将表达式优化为:

++i; // Usually the same as i++ + 1 if compiler has enough knowledge

因为后缀增量后加一个可以被视为前缀增量,而后不加一。

要点是,由编译器来确保副作用有时发生,这可能在计算+之前或之后;编译器只需要确保已存储或可以恢复i的原始值即可。

这里的各种扭曲似乎毫无意义(显然,++i是最好的选择,否则,i + 1;后跟++i最简单),但是它们通常是必要的在给定架构上使用硬件原子;如果该架构提供了fetch_then_add指令,则您希望将其实现为:

 auto tmp = fetch_then_add(i,1); // Returns original value of i,while atomically adding 1
 tmp + 1;

但是,如果它仅提供add_then_fetch指令,则您需要:

auto tmp = add_then_fetch(i,1); // Returns incremented value of i
(tmp - 1) + 1;

与许多事情一样,C ++标准没有强加优先顺序,因为实际的硬件并不总是协同工作。如果能够完成工作并按文档所述运行,则使用什么顺序都没有关系。

本文链接:https://www.f2er.com/3113291.html

大家都在问