在查看某个项目的代码时,我遇到了以下语句:
++x %= 10;
这个语句在C++中是否被定义良好,还是与之前提到的同类问题?
a[i] = i++
?
在查看某个项目的代码时,我遇到了以下语句:
++x %= 10;
这个语句在C++中是否被定义良好,还是与之前提到的同类问题?
a[i] = i++
?
根据C++11标准中的1.9程序执行/15
:
除非另有说明,否则各个运算符的操作数和各个表达式的子表达式的求值顺序是未指定的。
如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用同一标量对象的值进行的值计算是未指定的,行为就未定义。
在这种情况下,我认为++x
是副作用,而x %= 10
是值计算,所以你可能认为它是未定义行为。然而,赋值部分(5.17 /1
)有如下说明(我的加粗):
在所有情况下,赋值都在右操作数和左操作数的值计算之后排序,并且在赋值表达式的值计算之前。
因此,这意味着在赋值之前,两边的计算顺序已经确定,在赋值的结果可用之前也已经确定。由于标准还规定(5.17 /7
)x OP = y
与x = x OP y
相同,但只计算一次x
,所以这实际上是一种定义良好的行为,因为它等同于:
++x = Q % 10; // where Q is the value from ++x, not evaluated again.
那么唯一的问题是评估赋值运算符的哪一侧,因为它们不是按顺序执行的。然而,在这种情况下,我认为这并不重要,因为这两个将具有相同的效果:
++x = Q % 10; // LHS evaluated first
Q = ++x % 10; // RHS evaluated first
那是我的标准解读。虽然我有相当多的复杂文件解码经验,但可能会漏掉某些内容 - 我不认为会发生这种情况,因为我们在这里进行了热烈的讨论 :-) ,而且我认为我们已经确定了相关部分。
但是,无论是否规范定义,像那样编写代码的好程序员是不应该的。自从PDP小型机的低内存/小存储时代以来已经过去了很长时间,是时候将我们的代码编写成易于阅读的形式了。
如果您想要增量再取模,使用x = (x + 1) % 10
,即使只是为了让下一个可怜的Joe更容易理解那段代码。
++x
中,对x
的修改在表达式的值之前被排序。 - Deduplicator++lvalue
: "结果是更新后的操作数;它是一个左值。" 你还想要更详细明确的吗? - Deduplicatori = ++i + 1;
是未定义的。编译器可以构建代码来处理++x %= 10
,将x
存入寄存器中,对其进行递增操作,将结果移动到另一个寄存器中进行移位运算,再进行除法运算,将结果移动到x
的内存位置,并将之前计算出的递增结果移动到同一内存地址。该操作在C++03抽象机器中未定义,在接近底层的情况下,会有很多意外情况发生。 - Wintermute简而言之:由于在赋值之前,保证了 x
的递增,因此这是一个定义明确的过程。
[intro.execution]/15:
除非有特别说明,否则单个操作符的操作数和单个表达式的子表达式的计算顺序是未定义的。
然而,[expr.ass]/1有所说明:
在所有情况下,赋值都在右操作数和左操作数的值计算之后进行,并在赋值表达式的值计算之前进行。
因此,这构成了第一个引用中的例外。此外,如[expr.pre.incr]1所述,++x
等同于x += 1
,它也被上述引用所覆盖:赋值在值计算之前被排序。因此对于++x
,递增在值计算之前被排序。
考虑到这一点,不难看出,在++x %= 10
中,赋值之前已经完成了递增。
因此,递增副作用在赋值副作用之前被排序,因此所有涉及的副作用都被排序。
换句话说,标准强制执行以下排序:
++x
和10
,它们的顺序未定义,但10
只是一个文字,所以这里不相关。
++x
:
x
的值递增。x
的lvalue。x
更新的值对10
取模,并将其分配给x
。因此,如果对标量对象的副作用与同一标量对象的另一个副作用或使用相同标量对象值的值计算不是按顺序进行的,则行为是未定义的。
由于副作用和值计算是有序的,因此上述规则不适用。
1并非[expr.post.incr],那将是后缀递增运算符!
让我们来看一下单目递增运算符:
5.3.2 递增和递减 [expr.pre.incr]
1 前缀
++
的操作数通过加 1 来修改,或者如果它是bool
则设置为true
(此用法已被弃用)。操作数必须是可修改的左值。操作数的类型必须是算术类型或指向完整定义的对象类型的指针。结果是更新后的操作数;它是一个左值,如果操作数是位域,则是一个位域。如果x
不是bool
类型,则表达式++x
等同于x+=1
。
[...]
因此,所有与该单目运算符相关的计算和副作用都在其值之前安排好了,因此不会造成混乱。
现在只需对该左值进行%= 10
的评估即可。只有对常量进行评估可能是并发的(这不可能造成任何伤害),其余部分严格按照其他所有内容之后的顺序进行。
;
,没有任何东西能够明确地表示“严格之后”。 - v.oddou++x %= 10;
++x
然后 %=
是有序的。 - haccks++x
在其副作用上被排序,因为该值必须按标准排序,并且运算符返回一个lvalue
。这一点并不明显,但现在我看到了。 - v.oddou我将提供一个替代性答案,不引用好书,因为我认为稍微改写一下就很明显了。
++x %= 10; // as stated
x += 1 %= 10; // re-write the 'sugared' ++x
+=
)本身就是一个左值,因此不应该有任何疑问,通过进一步的简化,表达式为:(x = x+1) %= 10 // += -> =1+
x = (x+1) % 10 // assignment returns reference to incremented x
x = (x=x+1) % 10
。在序列点之间进行了两次赋值 -> 未定义。 - v.oddou=
在第一个之前完成。->已定义。 - paxdiablo%=
的问题——您的评论根本没有解决完整表达式上左侧是什么的问题。我并不是说您错了——我是说,如果没有参考资料,您的评论基本上相当于说“这里没有什么可看的,大家都散了吧”。 - frasnian前缀递增 (++x) 的优先级高于取模赋值 (%=)。语句:++x %= 10; 可以表示为:
++x;
x%= 10;