为什么f(i = -1,i = -1)未定义行为? [英] Why is f(i = -1, i = -1) undefined behavior?

查看:201
本文介绍了为什么f(i = -1,i = -1)未定义行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在阅读关于违反评估次序的订单,他们给出一个令我困惑的例子。 / p>

I was reading about order of evaluation violations, and they give an example that puzzles me.


1)如果对标量对象的副作用相对于同一标量对象上的另一个副作用未排序,那么行为是未定义的。

1) If a side effect on a scalar object is un-sequenced relative to another side effect on the same scalar object, the behavior is undefined.

// snip
f(i = -1, i = -1); // undefined behavior


在此上下文中, i

In this context, i is a scalar object, which apparently means


算术类型.1),枚举类型,指针类型,指向成员类型的指针(3.9.2),std :: nullptr_t和这些类型的cv限定版本(3.9.3)统称为标量类型。

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.

在这种情况下,我看不到语句是如何含糊的。在我看来,无论第一个或第二个参数是否先被评估, i 最终为 -1 两个参数也都是 -1

I don’t see how the statement is ambiguous in that case. It seems to me that regardless of if the first or second argument is evaluated first, i ends up as -1, and both arguments are also -1.

有人可以澄清吗?

我真的很感谢所有的讨论。到目前为止,我很喜欢 @ harmic的回答很多,因为它暴露了定义这个语句的陷阱和复杂性,尽管它是多么直接看第一眼。 @ acheong87 指出了使用引用时出现的一些问题,但我认为这与此问题的无序副作用方面正交。

I really appreciate all the discussion. So far, I like @harmic’s answer a lot since it exposes the pitfalls and intricacies of defining this statement in spite of how straight forward it looks at first glance. @acheong87 points out some issues that come up when using references, but I think that's orthogonal to the unsequenced side effects aspect of this question.

,我将总结主要观点/答案。首先,让我一个小小的指点,为什么可以有紧密相关但微妙的不同含义,即为什么,为什么和为了什么目的

Since this question got a ton of attention, I will summarize the main points/answers. First, allow me a small digression to point out that "why" can have closely related yet subtly different meanings, namely "for what cause", "for what reason", and "for what purpose". I will group the answers by which of those meanings of "why" they addressed.

这里的主要答案来自 Paul Draper ,其中 Martin J 提供了一个类似但不是广泛的答案。 Paul Draper的回答归结为

The main answer here comes from Paul Draper, with Martin J contributing a similar but not as extensive answer. Paul Draper's answer boils down to


这是未定义的行为,因为它没有定义行为。

It is undefined behavior because it is not defined what the behavior is.

在解释C ++标准所说的方面,答案是非常好的。它还解决了UB的一些相关情况,例如 f(++ i,++ i); f(i = 1,i = 1); 。在第一个相关案例中,不清楚第一个参数应该是 i + 1 还是第二个 i + 2 或反之亦然;在第二个,不清楚在函数调用之后 i 是否应该是1或-1。这两种情况都是UB,因为它们符合以下规则:

The answer is overall very good in terms of explaining what the C++ standard says. It also addresses some related cases of UB such as f(++i, ++i); and f(i=1, i=-1);. In the first of the related cases, it's not clear if the first argument should be i+1 and the second i+2 or vice versa; in the second, it's not clear if i should be 1 or -1 after the function call. Both of these cases are UB because they fall under the following rule:


如果对标量对象的副作用相对于另一侧

If a side effect on a scalar object is unsequenced relative to another side effect on the same scalar object, the behavior is undefined.

因此, f(i = -1, i = -1)也是UB,因为它属于相同的规则,尽管程序员的意图是(IMHO)是明显和明确的。

Therefore, f(i=-1, i=-1) is also UB since it falls under the same rule, despite that the intention of the programmer is (IMHO) obvious and unambiguous.

Paul Draper在他的结论中也明确指出:

Paul Draper also makes it explicit in his conclusion that


是。是否定义?

Could it have been defined behavior? Yes. Was it defined? No.

这会让我们回答为什么原因/目的是 f(i = 1,i = -1)留作未定义的行为?

which brings us to the question of "for what reason/purpose was f(i=-1, i=-1) left as undefined behavior?"

虽然在C ++标准中有一些疏忽(可能是粗心),但是很多遗漏都是非常合理的,并且具有特定的目的。虽然我知道,目的通常是使编译器作家的工作更容易,或更快的代码,我主要是感兴趣,知道是否有一个很好的理由离开 f(i = -1,i = -1) 为UB。

Although there are some oversights (maybe careless) in the C++ standard, many omissions are well-reasoned and serve a specific purpose. Although I am aware that the purpose is often either "make the compiler-writer's job easier", or "faster code", I was mainly interested to know if there is a good reason leave f(i=-1, i=-1) as UB.

harmic supercat 提供主要为UB提供原因的回答。 Harmic指出,优化编译器可能将表面上原子分配操作分解为多个机器指令,并且可能进一步交织这些指令以获得最佳速度。这可能会导致一些非常令人惊讶的结果: i 在他的场景中最终为-2!因此,危害表明如果操作不顺序,如何将相同的值多次分配给变量可能会产生不良影响。

harmic and supercat provide the main answers that provide a reason for the UB. Harmic points out that an optimizing compiler that might break up the ostensibly atomic assignment operations into multiple machine instructions, and that it might further interleave those instructions for optimal speed. This could lead to some very surprising results: i ends up as -2 in his scenario! Thus, harmic demonstrates how assigning the same value to a variable more than once can have ill effects if the operations are unsequenced.

supercat提供了一个相关的展示试图得到 f(i = -1,i = -1)的陷阱看起来像它应该做的陷阱。他指出,在某些体系结构上,存在对同一内存地址的多个同时写入的硬限制。如果我们处理比 f(i = -1,i = -1)更小的东西,编译器可能很难捕捉到这个。

supercat provides a related exposition of the pitfalls of trying to get f(i=-1, i=-1) to do what it looks like it ought to do. He points out that on some architectures, there are hard restrictions against multiple simultaneous writes to the same memory address. A compiler could have a hard time catching this if we were dealing with something less trivial than f(i=-1, i=-1).

davidf 还提供了一个与harmic的非常类似的交错指令示例。

davidf also provides an example of interleaving instructions very similar to harmic's.

虽然伤害,supercat和davidf'的例子有些勉强,但它们仍然提供了一个明确的原因,为什么 f(i = -1,i = -1)应该是未定义的行为。

Although each of harmic's, supercat's and davidf' examples are somewhat contrived, taken together they still serve to provide a tangible reason why f(i=-1, i=-1) should be undefined behavior.

我接受了harmic的回答,因为它做了最好的工作解决所有的意义,

I accepted harmic's answer because it did the best job of addressing all meanings of why, even though Paul Draper's answer addressed the "for what cause" portion better.

JohnB 指出,如果我们考虑重载的赋值运算符(而不是纯标量),那么我们也可能遇到麻烦。

JohnB points out that if we consider overloaded assignment operators (instead of just plain scalars), then we can run into trouble as well.

推荐答案

由于操作无序,没有什么可以说执行赋值的指令不能交叉。这可能是最佳的,这取决于CPU架构。引用页面指出:

Since the operations are unsequenced, there is nothing to say that the instructions performing the assignment cannot be interleaved. It might be optimal to do so, depending on CPU architecture. The referenced page states this:


如果A在B之前没有排序,B没有在A之前排序,那么
两种可能存在:

If A is not sequenced before B and B is not sequenced before A, then two possibilities exist:


  • 对A和B的评估无效:它们可能以任何顺序执行,
    编译器可以交错包括A和B的CPU指令)

  • evaluations of A and B are unsequenced: they may be performed in any order and may overlap (within a single thread of execution, the compiler may interleave the CPU instructions that comprise A and B)

A和B的评估是不确定排序的:以任何顺序执行,但不能重叠:A将在B之前完成
,或者B将在A之前完成。该顺序可以是与下一次评估相同表达式相反的
。 / p>

evaluations of A and B are indeterminately-sequenced: they may be performed in any order but may not overlap: either A will be complete before B, or B will be complete before A. The order may be the opposite the next time the same expression is evaluated.

这本身似乎不会造成问题 - 假设所执行的操作是将值-1存储到存储器位置。但是也没有什么可说的是,编译器不能将其优化为具有相同效果的单独的一组指令,但是如果操作与在相同存储器位置上的另一操作交织,则编译器可能失败。

That by itself doesn't seem like it would cause a problem - assuming that the operation being performed is storing the value -1 into a memory location. But there is also nothing to say that the compiler cannot optimize that into a separate set of instructions that has the same effect, but which could fail if the operation was interleaved with another operation on the same memory location.

例如,假设将内存置零,然后递减,与加载值-1相比,效率更高。然后:

For example, imagine that it was more efficient to zero the memory, then decrement it, compared with loading the value -1 in. Then this:

f(i=-1, i=-1)

可能会变成:

clear i
clear i
decr i
decr i

现在我是-2。

这可能是一个假的例子,但它是可能的。

It is probably a bogus example, but it is possible.

这篇关于为什么f(i = -1,i = -1)未定义行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆