如何确定在其他人之前"排序"的内容?

Laz*_*zer 20 c++ language-lawyer c++11

我在C++ 11中经历了关于Undefined Behavior和Sequenced [Before/After]关系的优秀答案.我理解二元关系概念,但我不知道管理测序的新规则是什么.

对于这些熟悉的示例,新的排序规则如何应用?

  1. i = ++i;
  2. a[++i] = i;

更具体地说,新的C++ 11排序规则是什么?

我正在寻找一些规则(这个是完全组成的)

lhs一个的'='语句总是前的测序rhs,并且首先是这样评价.

如果这些标准本身可用,有人可以在这里引用相同的内容吗?

Ant*_*ams 13

测序,之前的关系,以及有关它的规则是一对序列点前规则"整理",以一致的方式与其他内存模型关系,如定义的之前发生同步,以使之能精确指定在哪些情况下可见的操作和效果.

对于简单的单线程代码,规则的后果不变.

让我们从您的示例开始:

1. i = ++i;

如果i是内置类型,int那么没有涉及函数调用,一切都是内置运算符.因此有四件事情发生:

(一)值计算++i,它是原始值的-i的 +1

(b)在副作用++i,其中存储原始值的-i的 +1i

(c)赋值的值计算,它只是存储的值,在这种情况下是值计算的结果++i

(d)赋值的副作用,它将新值存储到i

所有这些事情都按顺序排列 - 在下面的完整表达之前.(即声明的最后一个分号都是完整的)

由于++i等同于i+=1,所述副作用存储值被测序-之前值计算++i,因此(b)的测序-前的(a).

值计算的分配的两个操作数的是测序-之前值计算的分配本身的,并且被依次测序-之前副作用存储所述值的.因此,(a)(c)之前测序,并且(c)在测序之前(d)测序.

因此,我们有(b) - >(a) - >(c) - >(d),这在新规则下是可以的,而在C++ 98下则不行.

如果i是a class,那么表达式将是i.operator=(i.operator++()),或者i.operator=(operator++(i)),在调用之前,调用的所有效果都会operator++排序operator=.

2. a[++i] = i;

如果a是一个数组类型,并且i是一个int,那么表达式又有几个部分:

(一)值计算i

(b)在值计算++i

(c)在副作用++i,其中存储的新值回i

(d)的值计算a[++i],它返回一个左值对的元件a由所述索引值计算++i

(e)赋值的值计算,它只是存储的值,在这种情况下是值计算的结果i

(f)赋值的副作用,它将新值存储到数组元素中a[++i]

同样,所有这些事情都按照以下完整表达式排序.(即声明的最后一个分号都是完整的)

再次,由于++i相当于i+=1,所述副作用存储值被测序-之前值计算++i,因此(c)中进行测序,前(b)中.

值计算数组索引的++i是*测序-before`的值计算元件选择的,因此(b)的测序-之前(d).

值计算的分配的两个操作数的是测序-之前值计算的分配本身的,并且被依次测序-之前副作用存储所述值的.因此,(a)和(d)(e)之前测序,并且(e)在测序之前(f)测序.

因此,我们有两个序列:(a) - >(d) - >(e) - >(f)和(c) - >(b) - >(d) - >(e) - >(f).

不幸的是,(a)和(c)之间没有排序.因此,一个副作用,其存储到i未测序相对于一个值计算i,以及将码表现出不确定的行为.这再次由1.9p15的C++ 11标准给出.

如上所述,如果i是类类型,那么一切都很好,因为运算符变为函数调用,这会强制执行排序.

规则

规则相对简单:

  1. 在运算符本身的值计算之前,对内置运算符的参数的值计算进行排序.

  2. 副作用内置赋值运算符或预增量运营商都测序,之前值计算结果.

  3. 在该运算符的副作用之前,对任何其他内置运算符的值计算进行排序.

  4. 值计算 副作用内置逗号操作符的左侧被测序,之前值计算 副作用的右手边.

  5. 在下一个完整表达式之前,对完整表达式的所有值计算副作用进行排序.

  6. 在函数中的第一个完整表达式之前,对函数调用的参数的值计算副作用进行排序.

  7. 计算结果的之前,对函数内部的所有内容的值计算副作用进行排序.

  8. 对于完整表达式中的任何两个函数调用,在调用另一个函数之前对其结果的值计算进行排序,反之亦然.如果没有其他规则指定排序,编译器可以选择.

    因此,在a()+b(),无论a()测序-之前 b(),或者b()之前测序- a(),但没有规则来指定.

  9. 如果有两个副作用修改同一个变量,并且两个副作用都没有按顺序排在另一个之前,则代码具有未定义的行为.

  10. 如果存在修改变量的副作用,以及读取该变量的值计算,并且两者都没有在另一个之前排序,则代码具有未定义的行为.


650*_*502 6

在我看来,这比规则点的旧规则要复杂得多,而且我不是100%肯定我理解正确...无论如何,IIUC这一切归结为如果得到你需要的价值副作用已经应用了.

第一个案例

i = ++i;
Run Code Online (Sandbox Code Playgroud)

在这里做分配你需要正确部分的值,要获得该值,你需要已经应用了副作用; 因此,这里的分配在增量后排序,一切都很好.这里重要的一点是,要进行分配,您需要RHS的值,只需要LHS 的地址.

回顾一下:

  1. 赋值在&i和之后排序++i
  2. ++i 在增量后排序
  3. (传递性)赋值在递增后排序

i在增量之后,值只读取一次.它被写入两次,一次是增量,一次是赋值,但这两个操作是按顺序排序的(首先是增量,然后是赋值).

第二种情况

a[++i] = i;
Run Code Online (Sandbox Code Playgroud)

在这里你需要iRHS的值和++iLHS 的值.然而,这两个表达式没有排序(赋值运算符没有强制执行排序),因此结果是未定义的.

回顾一下:

  1. 赋值在&a[++i]和之后排序i
  2. &a[++i] 之后排序 ++i
  3. ++i 在增量后排序

这里的值i被读取两次,一次用于LHS,一次用于RHS.LHS部分也进行了修改(增量).然而,该写访问和赋值RHS的读访问不是相互排序的,因此该表达式是UB.

最后的咆哮

让我再说一遍,我不确定我刚刚说过什么......我的强烈意见是,这种新方法在前后方法中难以理解.新的规则有希望只能使一些UB之前的表达式得到很好的定义(并且UB是最糟糕的结果),但它也使得规则更加复杂(它只是"不要在序列点之间改变两次相同的事情" "......你不必做一个心理拓扑排序来猜测某些东西是不是UB).

从某种意义上说,新规则对C++ 程序没有任何损害(UB是敌人,现在这个领域的UB较少)但是通过增加复杂性对语言造成了损害(并且肯定C++不需要的东西增加了复杂性) ).

还要注意,有趣的++i是返回的值是一个l值(这就是为什么++ ++ i是合法的),所以它基本上是一个地址,并且在逻辑上不需要在增量之后对返回值进行排序.但标准是这样说的,这是你需要燃烧到神经元的规则.当然要有一个"可用",++i你希望值的用户获得更新的值,但是仍然只要++操作员看到事物(它返回一个不受增量影响的地址),这种排序不是正式需要的.

使用新规则,您不仅需要进行心理拓扑排序以查看表达式是否有效,而且还需要使用您需要记忆的任意序列关系来执行此操作.

虽然你作为一名程序员当然希望永远不会编写多次改变相同值的代码而没有一个清晰的序列,但你仍然会遇到其他程序员编写的代码中的错误......事情并不那么清楚你现在需要更加努力地去理解某些东西是否合法C++.

  • 抱歉,但我仍然不明白,特别是这一部分 - "**这里要做的任务你需要正确部分的价值,并且要获得该值,你需要已经应用了副作用,因此这里的任务是增量后排序..**".为什么`=`在第一个例子中强加了一个排序而在第二个例子中没有强制排序?谢谢! (2认同)
  • IMO忘记新规则是有道理的,只要选择好旧的"你不应该同时读取和修改同一个完整表达式中的内存位置".它使代码更清晰,您不必担心UB. (2认同)
  • @AnthonyWilliams:没有.在`i = ++ i`我觉得你的答案是错的.5.17.1(N3126 = 10-0116)表示赋值表达式本身的值计算在赋值后进行排序.`++ i`相当于`(i + = 1)`(5.3.2),因此它的值计算在增量后排序.因此,在增量分配和表达式有效之后,`i = ++ i`中的主要赋值由传递性排序.事实上,有人声称你的C++体验可能会出错,这也证实了(虽然有点传闻)我对复杂性的咆哮并不是完全愚蠢的. (2认同)