Chu*_*dad 16 c++ side-effects sequence-points language-lawyer
很抱歉再次打开这个话题,但是考虑这个话题本身已经开始给我一个未定义的行为.想要进入定义明确的行为区域.
特定
int i = 0;
int v[10];
i = ++i; //Expr1
i = i++; //Expr2
++ ++i; //Expr3
i = v[i++]; //Expr4
Run Code Online (Sandbox Code Playgroud)
我认为上面的表达式(按此顺序)为
operator=(i, operator++(i)) ; //Expr1 equivalent
operator=(i, operator++(i, 0)) ; //Expr2 equivalent
operator++(operator++(i)) ; //Expr3 equivalent
operator=(i, operator[](operator++(i, 0)); //Expr4 equivalent
Run Code Online (Sandbox Code Playgroud)
现在来到这里的行为是来自C++ 0x的重要引用.
$ 1.9/12-"表达式(或子表达式)的评估通常包括值计算(包括确定用于左值评估的对象的身份以及获取先前分配给对象以进行右值评估的值)和启动副作用".
$ 1.9/15-"如果标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值计算的值没有排序,则行为未定义."
[注意:与不同参数表达式相关的值计算和副作用未被排序. - 尾注]
$ 3.9/9-"算术类型(3.9.1),枚举类型,指针类型,指向成员类型的指针(3.9.2),std :: nullptr_t和这些类型的cv限定版本(3.9.3)统称为标量类型."
在Expr1中,对表达式i(第一个参数)的评估在对评估operator++(i)(具有副作用)方面没有考虑.
因此,Expr1具有未定义的行为.
在Expr2中,对表达式i(第一个参数)的评估在对评估operator++(i, 0)(具有副作用)的评估方面没有考虑.
因此,Expr2具有未定义的行为.
在Expr3中,在调用operator++(i)外部之前,必须完成对单个参数的评估operator++.
因此,Expr3具有良好定义的行为.
在Expr4中,表达式i(第一个参数)的评估对于operator[](operator++(i, 0)(具有副作用)的评估没有统计.
因此,Expr4具有未定义的行为.
这种理解是否正确?
PS在OP中分析表达式的方法不正确.这是因为,作为@Potatoswatter,注意 - "第13.6条不适用.参见13.6/1中的免责声明,"这些候选函数参与13.3.1.2中描述的运算符重载解析过程,并且不用于其他目的. "它们只是虚拟声明;内置运算符不存在函数调用语义."
Pot*_*ter 15
本机运算符表达式不等于重载的运算符表达式.将值绑定到函数参数时有一个序列点,这使得operator++()版本定义良好.但是对于本机类型的情况不存在.
在所有四种情况下,i在完整表达式内进行两次更改.由于没有,,||或&&出现在表达式中,那就是即时UB.
§5/ 4:
在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改其存储值一次.
§1.9/ 15:
在运算符的结果的值计算之前,对运算符的操作数的值计算进行排序.如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义.
但请注意,值计算和副作用是两个截然不同的事情.如果++i相当于i = i+1,那么+是值计算并且=是副作用.从1.9/12:
表达式(或子表达式)的评估通常包括值计算(包括确定用于glvalue评估的对象的身份以及获取先前分配给用于prvalue评估的对象的值)和启动副作用.
因此,虽然C++ 0x中的值计算比C++ 03更强排序,但副作用却不是.除非另有测序,否则相同表达中的两种副作用产生UB.
值的计算是由他们的数据依赖性反正有序,并没有副作用,其评价的顺序是不可见的,所以我不知道为什么的C++ 0x去说任何的麻烦,但是这只是意味着我需要阅读更多Boehm和朋友写的论文写道.
感谢Johannes处理我的懒惰,在我的PDF阅读器搜索栏中键入"已排序".无论如何,我上床睡觉并且最后两次编辑......对吧; v).
§5.17/ 1定义了赋值运算符
在所有情况下,分配的右侧和左侧的操作数的值计算后测序,并赋值表达式的值计算之前.
关于preincrement运算符的§5.3.2/ 1也说
如果x不是bool类型,则表达式++ x等效于x + = 1 [注意:请参阅...加法(5.7)和赋值运算符(5.17)...].
通过这种身份,++ ++ x是简写(x +=1) +=1.那么,让我们解释一下.
1远端RHS并下降到parens.1和值(prvalue)和地址(glvalue)x.x,这与子表达式的glvalue和prvalue结果相同.x +=1.所以,然后 1和3是明确定义的,2和4是未定义的行为,你可以期待.
我在N3126中搜索"sequenced"时发现的另一个惊喜是5.3.4/16,其中允许operator new在评估构造函数参数之前调用实现.这很酷.
约翰尼斯再次指出,i == ++i;在glvalue(又名地址)中,i模糊地依赖于++i.glvalue当然是一个值i,但我不认为1.9/15是为了包含它,原因很简单,因为命名对象的glvalue是常量,并且实际上不能有依赖.
对于信息丰富的稻草人,请考虑一下
( i % 2? i : j ) = ++ i; // certainly undefined
Run Code Online (Sandbox Code Playgroud)
这里,LHS的glvalue =取决于对prvalue的副作用i.地址i不是问题; 结果?:是.
也许一个好的反例是
int i = 3, &j = i;
j = ++ i;
Run Code Online (Sandbox Code Playgroud)
这里j有一个不同于(但相同)的glvalue i.这是明确定义的,但i = ++i不是吗?这代表了一个简单的转换,编译器可以应用于任何情况.
1.9/15应该说
如果相对于相同标量对象的另一个副作用或使用相同标量对象的prvalue的值计算,对标量对象的副作用未被排序,则行为未定义.