很抱歉再次打开这个主题,但是思考这个主题本身已经开始给我一个未定义的行为。希望进入行为定义良好的区域。
鉴于
1 2 3 4 5 6
| int i = 0;
int v[10];
i = ++i; //Expr1
i = i++; //Expr2
++ ++i; //Expr3
i = v[i++]; //Expr4 |
我把上面的表达(按这个顺序)看作
1 2 3 4
| 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 |
这里的行为是C++的重要引文。
$1.9/12-"Evaluation of an expression
(or a sub-expression) in general
includes both value computations
(including determining the identity of
an object for lvalue evaluation and
fetchinga value previously assigned to
an object for rvalue evaluation) and
initiation of side effects."
$1.9/15-"If a side effect on a scalar
object is unsequenced relative to
either another side effect on the same
scalar object or a value
computation using the value of the
same scalar object, the behavior is
undefined."
[ Note: Value computations and side
effects associated with different
argument expressions are unsequenced.
—end note ]
$3.9/9-"Arithmetic types (3.9.1),
enumeration types, pointer types,
pointer to member types (3.9.2),
std::nullptr_t, and cv-qualified
versions of these types (3.9.3) are
collectively called scalar types."
在expr1中,对表达式i的评价(第一个论点)与对expression operator++(i)的评价(有副作用)没有排序。
因此expr1具有未定义的行为。
在expr2中,表达式i的计算(第一个参数)与表达式operator++(i, 0)的计算(有副作用)没有排序。
因此,expr2具有未定义的行为。
在expr3中,在调用外部operator++之前,需要完成对单个参数operator++(i)的评估。
因此expr3具有定义良好的行为。
在expr4中,对表达式EDOCX1(第一个参数)的评估与对EDOCX1(第7个参数)的评估(具有副作用)没有排序。
因此,expr4具有未定义的行为。
这种理解正确吗?
P.S.分析OP中表达式的方法不正确。这是因为,正如@potatoswatter,注释-"第13.6条不适用。参见13.6/1中的免责声明,"这些候选函数参与了13.3.1.2中所述的运算符重载解决过程,并且没有用于其他目的。"它们只是一个虚拟声明;对于内置运算符不存在函数调用语义。
- +!:好问题。我会留意答案的。
- @丘布斯达:我同意@james mcnellis在他的回答中所说的(他后来删掉了)。这4个表达式都在C++ 0x[imHO]中调用了ub。我想你应该在CSC++上问这个问题(comp.std.c++)。:)
- @Prason Saurav:为什么expr3有未定义的行为?我认为这应该是好的。gcc/comeau/llvm(demo)也可以在没有任何警告的情况下编译。
- 这是因为与++内〔1〕和++外〔1〕有关的副作用没有相对排列(尽管值计算是按顺序排列的)。:)
- 看看这个。有人提到,Some more complicated cases are not diagnosed by -Wsequence-point option, and it may give an occasional false positive result,.....。
- @ Prasoon如果你说这是未定义的行为,你必须拿出支持该点的C++ 0x(当前N3126)的措辞。引用詹姆斯·坎兹的话并不能证明这一点,伙计。
- 我和"Kai Uwe Bux"已经展示了如何定义从各种C++ 0x规则。意图可能是,也可能不是措辞所反映的,但这是一个完全不同的故事。
- Johannes Schaub-Litb:当你在那里的时候,请告诉我们,这是正确的方式来形象化这些表达式,还是我错过了这种思维的任何情况(就操作函数而言,即使这些在实用性上不存在,对本地类型的调用也是如此),除了13.6/18美元。
- 例如,这种思想还解释了++i=0(operator=(operator++(i),0)以及定义良好的行为。
本机运算符表达式不等价于重载的运算符表达式。在值与函数参数的绑定上有一个序列点,这使得operator++()版本得到了很好的定义。但对于本机类型的情况,这是不存在的。
在这四种情况下,i在完全表达中变化了两次。由于表达式中没有出现,、||或&&,这就是即时ub。
第5/4节:
Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression.
为C++0x编辑(更新)
1.1.9/15:
The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
但是请注意,值计算和副作用是两个不同的东西。如果++i等于i = i+1,则+为数值计算,=为副作用。从1.9/12:
Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects.
因此,尽管C++0x中的值计算比C++ 03、更为严格,但副作用不是。在同一表达式中的两个副作用,除非另有排序,就产生UB。
无论如何,价值计算都是根据它们的数据依赖性排序的,而且副作用不存在,它们的评价顺序是不可观察的,所以我不确定为什么c++0x会去说什么,但是这意味着我需要读更多的论文,Boehm和朋友写道。
编辑第3页:
感谢Johannes处理我的懒惰,在我的PDF阅读器搜索栏中输入"sequenced"。不管怎样,我要上床睡觉,然后开始最后两次编辑……对吧;v)。
§5.17/1定义分配运算符表示
In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.
另外,§5.3.2/1上的预增量运算符表示
If x is not of type bool, the expression ++x is equivalent to x+=1 [Note: see … addition (5.7) and assignment operators (5.17) …].
根据这个身份,++ ++ x是(x +=1) +=1的缩写。所以,让我们来解释一下。
- 在远端右侧评估1,然后下降到Parens。
- 评估1的内部值和x的值(prvalue)和地址(glvalue)。
- 现在我们需要+=子表达式的值。
- 我们已经完成了该子表达式的值计算。
- 在赋值值可用之前,必须对赋值副作用进行排序!
- 将新值赋给x,与子表达式的glvalue和prvalue结果相同。
- 我们现在不在森林里了。现在整个表达已经减少到x +=1。
所以,1和3是定义良好的,2和4是未定义的行为,这是您所期望的。
我在N3126中搜索"sequenced"时发现的另一个惊喜是5.3.4/16,在评估构造函数参数之前,允许实现调用operator new。那太酷了。
编辑4:(哦,我们编织的网多乱啊)
约翰内斯再次指出,在i == ++i;中,i的gl值(也就是地址)在很大程度上依赖于++i。glvalue当然是i的值,但我不认为1.9/15是为了包含它,因为命名对象的glvalue是常量,实际上不能有依赖性。
对于信息丰富的策略,请考虑
1
| ( i % 2? i : j ) = ++ i; // certainly undefined |
这里,=的lhs的gl值取决于i的pr值的副作用。i的地址没有问题;?:的结果是。
或许一个很好的反例是
1 2
| int i = 3, &j = i;
j = ++ i; |
这里,j的gl值不同于(但与)i的gl值。这是一个定义明确的问题吗,但i = ++i不是吗?这表示编译器可以应用于任何情况的简单转换。
1.9/15应该说
If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the prvalue of the same scalar object, the behavior is undefined.
- 对不起,我在我的帖子里提到了C++0X
- @ Chubdad:C++0X没有序列点…
- @potato afaik这个词只是"序列点"这个词,已经被弃用以支持更清晰的措辞,但它仍然存在。
- @nulluser:有一个排序的概念,但是用C语言表达机器处于完全确定状态或不确定状态的方法已经不复存在了。
- 我也这么认为。但是stackoverflow.com/questions/3850040/…给思维过程带来了一些变化。"我=++i;"现在有点困惑,我还没能把我的想法理清。在这篇文章中,我试图验证我的思考过程是否正确(以等价于运算符的方式思考)。
- @ PATATOSWATTER:所有四个表达式在C++ 0x中调用UB
- @chubsdad:++i的值是=运算符的操作数,因此至少根据本段,效果被排序。不管是否有别的东西使它成为UB,我会检查下一个Prason的链接。
- @potatoswatter:"本机运算符表达式不等于重载运算符表达式"是标准表达式吗?您在这里讨论的是内置/本机/默认运算符=和上面在用户代码中定义的重载运算符=之间的值到函数参数绑定的序列点位置的差异吗?有没有可能举一个"本机类型"的例子?所以,如果我定义了一个重载的++并编写了一个I++,那么它就是定义良好的行为——否则就没有定义?这适用于C++ 98/03和C++ 0x?
- @prason:不,那些未排序的示例使用post increment。Johannes给出了一个预增量示例i = v[++i],并认为存储(i=i+1)的副作用相对于下一个明确的赋值是不排序的……这是另一个论点,也许是一个好论点。但我现在太困了,无法独立评估。
- @彼得:关于重载运算符的子句的开头和表达式的子句的开头都讨论了序列点的区别。例如,&&、||和,的顺序非常不同。重载运算符是函数调用,而第5条运算符不是。(编辑:除了函数调用运算符。我现在去睡觉。)
- Potatoswatter:我不太确定,但仍然认为EDOCX1 7和EDOCX1 8个都是C++0x中的UB。阅读James Kanze的文章[在讨论结束时]。;)
- @普拉桑:是的,阅读Usenet的短篇文章是件很烦人的事情,但是&167;1.9/12和15确实非常清楚。更新答案。
- @ Prasoon,讨论链接到表达式3并没有根据草案调用C++ 0x中未定义的行为。即使是"詹姆斯坎兹"也同意这个显而易见的事情,在被告知了一百次之后(他怀疑"分配"是一个副作用…但如果不是修改,那是什么?)詹姆斯·坎兹很爱听别人写的东西。请注意,我是如何在讨论的中间写到"之前排序"是一个传递关系。他只是忽略了这一点,最后当其他人提到这一点时,他说:"噢,上校,你可能有点道理!".
- 有关expr3定义良好和expr3不定义的证据,请参阅:stackoverflow.com/questions/3690141/…
- @约翰内斯:我认为你的意思是expr1的不精确……无论如何,我看不出你如何计算expr3中第一个++的副作用与expr1中的=的顺序不同。两个++操作对两个不同的值生成两个不同的赋值,1.9/15不会对它们排序。
- 型@是的,我的意思是不完美的表达,对不起:)必须去工作。同时,在5.x中查找要分配的段落,所有段落都按顺序排列。西娅:)
- 型我们回来了。为什么1有明确的行为?这就是在Usenet讨论中被证明是错误的,不是吗?
- 型@chubdad:因为++i是一个等价于赋值表达式(5.3.2/1)的子表达式,其副作用是"在赋值表达式的值计算之前"排序的。(5.17/1)
- 型这也是我最初的感受。但是,usenet讨论论坛似乎认为expr1的行为是不正确的。@利特也支持这一点。
- 型@像@chubsdad所说的potatoswatter,1绝对是未定义的行为。是的,++i的副作用是在++i的值计算之前排序的。这将递增的副作用排序在"实际"分配副作用之前到i。但在i = ++i的左侧,我们也有一个i的值计算,相对于右侧的值计算没有排序。这就是它未定义的原因。注意,"值计算"不仅意味着"读取值",而且意味着"计算左值所指的对象"用于GLVALUE评估。请参阅上面的stackoverflow链接。
- 型啊,对不起,老家伙…
- 型@我明白你的意思了。
- 型@约翰内斯:对不起,我的连接中断了,我走了,忘了你在等……发布了最新消息。
- 型@我删除了我的答案,因为你详细阐述了你的理由。
- 型@约翰内斯:我想这是你的电话,我认为这是个好答案。你真的认为工作文件还在不断变化吗?你认为医生已经提交了吗,你想这样做吗?艾莉斯达尔没有回复我最近的几封邮件,所以我想我可能已经让他生气了;v)。
- 型@potatoswatter:j=++i;也应该是未定义的行为。J只是"我"的别名,不是吗?我(大写I)不理解这一点,继续表现出未定义的行为……:)
- 型@Chubsdad:尽管它是别名,但它的glvalue评估不需要对i进行glvalue评估。一般来说,评估一个引用并不需要原始对象在手边。没有理由它应该是ub,所以应该有一个简单的漏洞,或者转换成不是ub的代码。
- 型它的glvalue评估不需要i的glvalue评估。一般来说,评估一个引用不需要原始对象在手边:我真的对此表示怀疑。您对这个"引用"有任何引用吗?
- 型@Chubsdad:考虑一下函数f( int &i, int &j ) { j = ++ i; } … f( i, i );还是这么认为的?引用是别名,因为它的glvalue计算结果相同,而不是语法上引用原始对象。
- 型@potatoswatter:我的理解是"j"是"i"的别名,即使"i"不在范围内,只要"i"仍然是其最初的有效对象,就可以使用"j"。我不太确定我是否理解"glvalue的计算结果相同"与"glvalue相同"。
- 型参考5.5-"如果表达式最初具有类型"reference to t"(8.3.2,8.5.3),则在进行任何进一步分析之前,将类型调整为t。The expression designates the object or function denoted by the reference,表达式是左值或x值,具体取决于表达式。
- 型@丘布斯达:它用一个相同的左值来表示它;这就是它的长和短。lvalue到rvalue转换实现具有referent对象值的引用。引用不会告诉编译器去查看引用的变量并获取其左值,因为它可能不知道引用了什么变量。编译器计算引用的左值,该左值标识对象。如果你想进一步讨论这个问题,请打开一个新问题。
- 型@potatoswatter:你的愿望是我的命令:)(stackoverflow.com/questions/3870172/…)
- 我得到了它。但我仍然怀疑。让我们取"i=++i"。根据13.6/18,可将其视为"operator=(i,operator++(i))"。相对于同一个标量对象的值计算(第一个参数为"i"),标量对象上的副作用(第二个参数导致的"i")是未排序的。因此,行为应该是未定义的。你能告诉我为什么要从这个角度来定义它吗?
- @丘布斯达:按照标准,这是未定义的,正如约翰内斯解释的。(我希望他没有删除他的答案。)然而,编译器几乎不可能产生除所需之外的任何行为,因为假定的依赖值是一个常量。同样,第13.6条不适用。参见13.6/1中的免责声明,"这些候选函数参与了13.3.1.2中所述的运算符重载解决过程,并且没有用于其他目的。"它们只是一个虚拟声明;对于内置运算符不存在函数调用语义。
- @potatoswatter:那么你想改变你的帖子,因为它说expr1的格式很好吗?
- @不,最后一次编辑完全花在了expr1的讨论上,所以这个问题是明确的。我有点厌倦了。
- @chubsdad我想@potatoswatter指的是std说"使用相同标量对象的值"。如果只使用引用对象的左值,则不使用对象的值。尽管使用了奇怪的术语"值计算",但您必须实际读取该值。但是"值计算"中的"值"一词似乎不是指对象的值,而是指表达式的"值"。即表达式的glvalue或prvalue结果。但我真的认为在这个问题上,标准应该更加明确。
- @约翰内斯·肖布-利特:哦。现在我明白了。这对我来说有点太神秘了。我想我已经开始掌握核心问题的窍门了,但现在还没有。
- 我发现了两个支持这个答案的DRS:open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html_637和open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html_222
- @ PrasoOnSururav EDCOX1(0)你错了,因为EDCOX1(1)在C++0x.StAccExoLo.COM/问题/ 17400137/& Helip中被完全定义;
在考虑类似上面提到的表达式时,我发现可以想象一台机器,其中内存具有互锁,因此将内存位置作为读-修改-写序列的一部分进行读取将导致除序列的结束写入之外的任何尝试读取或写入暂停,直到序列完成。这样的一台机器几乎不会是一个荒谬的概念;事实上,这样的设计可以简化许多多线程代码场景。另一方面,如果"x"和"y"是对同一个变量的引用,那么类似"x=y++;"的表达式在这样的机器上可能会失败,并且编译器生成的代码执行了诸如read和lock reg1=y、reg2=reg1+1、write x=reg1、write和unlock y=reg2之类的操作。在处理器上,这将是一个非常合理的代码序列,其中写入新计算的值将导致管道延迟,但如果Y别名为同一个变量,则对X的写入将锁定处理器。