未排序的值计算(也称为序列点)
抱歉再次打开这个主题,但是思考这个主题本身已经开始给我一种未定义的行为。想要进入行为明确的区域。
鉴于
int i = 0;
int v[10];
i = ++i; //Expr1
i = i++; //Expr2
++ ++i; //Expr3
i = v[i++]; //Expr4
我认为上述表达式(按顺序)为
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++ 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++
之前完成。 code> 被调用。因此 Expr3 具有明确定义的行为。
在 Expr4 中,表达式
i
(第一个参数)的求值相对于运算符的求值是无序的[](operator++(i, 0)
(有副作用)。因此 Expr4 具有未定义的行为。
这种理解正确吗?
PS 分析表达式的方法为OP 中的内容不正确。这是因为,正如 @Potatoswatter 所指出的 - “第 13.6 条不适用。请参阅 13.6/1 中的免责声明,“这些候选函数参与 13.3.1.2 中描述的运算符重载解析过程,并且是不用于其他目的。“它们只是虚拟声明;对于内置运算符不存在函数调用语义。”
Sorry for opening this topic again, but thinking about this topic itself has started giving me an Undefined Behavior. Want to move into the zone of well-defined behavior.
Given
int i = 0;
int v[10];
i = ++i; //Expr1
i = i++; //Expr2
++ ++i; //Expr3
i = v[i++]; //Expr4
I think of the above expressions (in that order) as
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
Now coming to behaviors here are the important quotes from C++ 0x.
$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."
In Expr1, the evaluation of the expression
i
(first argument), is unsequenced with respect to the evaluation of the expessionoperator++(i)
(which has a side effect).Hence Expr1 has undefined behavior.
In Expr2, the evaluation of the expression
i
(first argument), is unsequenced with respect to the evaluation of the expessionoperator++(i, 0)
(which has a side effect)'.Hence Expr2 has undefined behavior.
In Expr3, the evaluation of the lone argument
operator++(i)
is required to be complete before the outeroperator++
is called.Hence Expr3 has well defined behavior.
In Expr4, the evaluation of the expression
i
(first argument) is unsequenced with respect to the evaluation of theoperator[](operator++(i, 0)
(which has a side effect).Hence Expr4 has undefined behavior.
Is this understanding correct?
P.S. The method of analyzing the expressions as in OP is not correct. This is because, as @Potatoswatter, notes - "clause 13.6 does not apply. See the disclaimer in 13.6/1, "These candidate functions participate in the operator overload resolution process as described in 13.3.1.2 and are used for no other purpose." They are just dummy declarations; no function-call semantics exist with respect to built-in operators."
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
本机运算符表达式不等同于重载运算符表达式。在值与函数参数的绑定处有一个序列点,这使得
operator++()
版本定义良好。但对于本机类型的情况而言,这种情况不存在。在所有四种情况下,
i
在完整表达式中更改了两次。由于表达式中没有出现、
、||
或&&
,因此即为即时 UB。§5/4:
针对 C++0x 进行编辑(已更新)
§1.9/15:
但请注意,值计算和副作用是两个不同的东西。如果
++i
相当于i = i+1
,则+
是值计算,=
是副作用。从 1.9/12 开始:因此,尽管 C++0x 中的值计算比 C++03 中的顺序更严格,但
副作用却并非如此。同一表达式中的两个副作用(除非另有顺序)会产生 UB。无论如何,值计算都是按它们的数据依赖关系排序的,而且,不存在副作用,它们的求值顺序是不可观察的,所以我不确定为什么 C++0x 会费劲去说什么,但这只是意味着我需要阅读更多 Boehm 和朋友写的论文。编辑#3:
感谢 Johannes 解决了我在 PDF 阅读器搜索栏中输入“sequenced”的懒惰问题。无论如何,我要去睡觉并起床进行最后两个编辑……对;v) 。
§5.17/1 定义赋值运算符说
关于预自增运算符的§5.3.2/1 还说
根据这个恒等式,
++ ++ x
是(x +=1) +=1
的简写。那么,让我们解释一下。1
并下降到括号中。1
以及x
的值 (prvalue) 和地址 (glvalue)。x
,该值与子表达式的泛左值和纯右值结果相同。x +=1
。所以, 1 和 3 是明确定义的,而 2 和 4 是未定义的行为,正如您所期望的那样。
我通过在 N3126 中搜索“sequenced”发现的唯一另一个惊喜是 5.3.4/16,其中允许实现在评估构造函数参数之前调用
operator new
。这很酷。编辑#4:(哦,我们编织了多么混乱的网络)
Johannes 再次指出,在
i == ++i;
中,i
的泛左值(又名地址)是模糊地依赖于++i
。泛左值肯定是i
的a值,但我不认为 1.9/15 打算包含它,原因很简单,命名对象的泛左值是常量,并且实际上不能具有依赖关系。对于信息丰富的稻草人,请考虑
这里,
=
的 LHS 的左值取决于i
纯右值的副作用。i
的地址没有问题;?:
的结果是。也许一个很好的反例是
这里
j
有一个与i
不同(但相同)的左值。这是明确定义的,但i = ++i
却不是?这表示编译器可以应用于任何情况的简单转换。1.9/15 应该说
Native operator expressions are not equivalent to overloaded operator expressions. There is a sequence point at the binding of values to function arguments, which makes the
operator++()
versions well-defined. But that doesn't exist for the native-type case.In all four cases,
i
changes twice within the full-expression. Since no,
,||
, or&&
appear in the expressions, that's instant UB.§5/4:
Edit for C++0x (updated)
§1.9/15:
Note however that a value computation and a side effect are two distinct things. If
++i
is equivalent toi = i+1
, then+
is the value computation and=
is the side effect. From 1.9/12:So although the value computations are more strongly sequenced in C++0x than C++03,
the side effects are not.Two side effects in the same expression, unless otherwise sequenced, produce UB.Value computations are ordered by their data dependencies anyway and, side effects absent, their order of evaluation is unobservable, so I'm not sure why C++0x goes to the trouble of saying anything, but that just means I need to read more of the papers by Boehm and friends wrote.Edit #3:
Thanks Johannes for coping with my laziness to type "sequenced" into my PDF reader search bar. I was going to bed and getting up on the last two edits anyway… right ;v) .
§5.17/1 defining the assignment operators says
Also §5.3.2/1 on the preincrement operator says
By this identity,
++ ++ x
is shorthand for(x +=1) +=1
. So, let's interpret that.1
on the far RHS and descend into the parens.1
and the value (prvalue) and address (glvalue) ofx
.x
, which is identical to the glvalue and prvalue result of the subexpression.x +=1
.So, then 1 and 3 are well-defined and 2 and 4 are undefined behavior, which you would expect.
The only other surprise I found by searching for "sequenced" in N3126 was 5.3.4/16, where the implementation is allowed to call
operator new
before evaluating constructor arguments. That's cool.Edit #4: (Oh, what a tangled web we weave)
Johannes notes again that in
i == ++i;
the glvalue (a.k.a. the address) ofi
is ambiguously dependent on++i
. The glvalue is certainly a value ofi
, but I don't think 1.9/15 is intended to include it for the simple reason that the glvalue of a named object is constant, and cannot actually have dependencies.For an informative strawman, consider
Here, the glvalue of the LHS of
=
is dependent on a side-effect on the prvalue ofi
. The address ofi
is not in question; the outcome of the?:
is.Perhaps a good counterexample is
Here
j
has a glvalue distinct from (but identical to)i
. Is this well-defined, yeti = ++i
is not? This represents a trivial transformation that a compiler could apply to any case.1.9/15 should say
在考虑上述表达式时,我发现想象一台内存具有互锁的机器很有用,这样作为读取-修改-写入序列的一部分读取内存位置将导致任何尝试读取或写入,除了最终写入序列,将被停止,直到序列完成。这样的机器绝不会是一个荒谬的概念。事实上,这样的设计可以简化许多多线程代码场景。另一方面,像“x=y++;”这样的表达式如果“x”和“y”引用同一变量,并且编译器生成的代码执行类似 read-and-lock reg1=y; 的操作,则在此类机器上可能会失败。 reg2=reg1+1;写x=reg1;写入并解锁 y=reg2。这在处理器上是一个非常合理的代码序列,其中写入新计算的值会造成管道延迟,但如果 y 别名为同一变量,则写入 x 会锁定处理器。
In thinking about expressions like those mentioned, I find it useful to imagine a machine where memory has interlocks so that reading a memory location as part of a read-modify-write sequence will cause any attempted read or write, other than the concluding write of the sequence, to be stalled until the sequence completes. Such a machine would hardly be an absurd concept; indeed, such a design could simplify many multi-threaded code scenarios. On the other hand, an expression like "x=y++;" could fail on such a machine if 'x' and 'y' were references to the same variable, and the compiler's generated code did something like read-and-lock reg1=y; reg2=reg1+1; write x=reg1; write-and-unlock y=reg2. That would be a very reasonable code sequence on processors where writing a newly-computed value would impose a pipeline delay, but the write to x would lock up the processor if y were aliased to the same variable.