Possible Duplicate:
Unsequenced value computations (a.k.a sequence points)
Undefined Behavior and Sequence Points
Operator Precedence vs Order of Evaluation
我仍在努力思考以下表达式如何导致未定义的行为:
经过这样的调查,我发现了以下问题:
序列点和运算符优先级之间的区别?0o
我通读了所有的答案,但在细节上仍有困难。其中一个答案将上述代码示例的行为描述为不明确,即如何修改a。例如,可以归结为以下两个方面:
究竟是什么使a的修改模糊不清?这是否与不同平台上的CPU指令有关,以及优化器如何利用未定义的行为?换句话说,由于生成的汇编程序,它似乎未定义?
我看不出编译器使用a=(a+1);a++;的原因,它看起来很奇怪,没有什么意义。编译器拥有什么才能使其如此工作?
编辑:
为了清楚起见,我确实理解正在发生的事情,我只是不理解当有关于运算符优先级的规则(本质上定义了表达式的计算顺序)时,如何定义它。在这种情况下,赋值最后发生,因此需要首先对a++进行评估,以确定要赋值给a的值。所以我期望的是,在固定后增量期间,首先修改a,然后生成一个值,分配回a(第二次修改)。但是操作员优先权的规则似乎使我的行为非常清楚,我找不到有任何"回旋空间"让它有未定义的行为。
- 您请求在没有序列点的情况下修改a两次:一次在分配中,一次作为++的副作用。标准没有具体说明你的意思。
- @Kerreksb:我已经知道a被修改了两次,我想问的是它到底有什么未定义的。"标准没有具体说明你的意思——什么?它指定了操作顺序和运算符优先级,使表达式的含义非常清楚。我可以看着它,然后依次知道发生了什么。
- 运算符优先级在解析表达式的几种可能方法之间消除了歧义(考虑到*p++,也就是说(*p)++,它没有帮助确定对象修改的顺序。
- 是的,伙计们,有副本,但是如果答案对我没有意义,副本对我有什么好处呢?我的目标是得到不同的答案,以更好地理解我。
- @罗伯特-这有什么重要的?您试图在同一表达式中更改EDOCX1的值(0)两次。你为什么要这么做?这绝对没有实际用途。
- @我从来没有说过我在实践中使用过这个。这是一个教育会议,以便更好地理解标准定义的C++语义。
- @罗伯特这个回答帮助我很好地理解了这一点。
- @罗伯特——但没有什么可理解的。:-)在现实世界中,不需要更新a两次,那么我们为什么要关心?++a工作正常,a = a++ + ++a - a--不工作。使用有效的那个!
- @罗伯特:"我的目标是得到不同的答案",然后你使用一个赏金,说你不理解当前关于这个问题的答案。
- 我认为你可能在试图过度分析形势。根据语言规则,您拥有的代码具有未定义的行为。此时,您不能应用任何逻辑,例如"如果它没有未定义的行为;它只能有一个含义,因此它不应该是未定义的"。就是这样。
- @查尔斯拜关于科学的最伟大的哲学之一:总是质疑事物。总有一个原因可以解释为什么有些东西是未定义的。当委员会决定不制定规则,否则将使它定义得很好时,我想知道他们在想什么情景。
- 但是编程标准并不是一种自然现象,你可以运用科学的方法和推理。这是人类设计的一套规则。
- @查尔斯巴利和政府和法律是一样的,但仅仅因为有规则存在,并不能使它们正确。就像我试图理解为什么某件事有规律一样,我想理解为什么某件事没有规律。你误解了我,你认为我在试图改变标准,或者希望标准有所不同。不,我想知道为什么没有定义。请讲清楚。
- 好吧,但是当我回复你的评论时,它仅仅是读到:"@charlesbailey,关于科学的最伟大的哲学之一:总是质疑事物。"我希望我的回答在这方面更有意义。
- @CharlesBailey编程仍然是一门科学,科学并不总是意味着某些事情必须是自然现象。但是C++是一定可以应用科学方法和推理的,标准是证明这一点的(它是这样的直接结果)。标准也不是一个固定的实体,它由于科学的质疑而进化(例如:C++ 11)(例如"我们怎样才能使它变得更好?")--现在别谈这个话题了,但这就是我的意思。
- @罗伯特戴利:如果你看一下我对我指定为副本的问题的回答,它给出了一些非常具体和(imo)非常合理的例子,说明你将如何以你讨论过的代码中的问题行为结束。如果你已经看过了,发现我的推理很难理解(或牵强附会),我会很高兴听到这一点。如果能澄清答案的话,我一点也不介意做些编辑。
你链接到的问题的第一个答案可以准确解释发生了什么。我会试着把它改成更清楚的措辞。
运算符优先级定义通过表达式计算值的顺序。表达式(a++)的结果很容易理解。
但是,修改变量a并不是表达式的一部分。是的,真的。这是你难以理解的部分,但这只是C和C++如何定义的部分。
表达式产生值,但某些表达式可能有副作用。表达式a = 1的值为1,但它也具有将变量a设置为1的副作用。至于C和C++如何定义事物,这是两个不同的步骤。同样,a++具有价值和副作用。
序列点定义在这些序列点之后计算的表达式何时可见副作用。运算符优先级与序列点无关。这就是C/C++定义事物的方式。
- 副作用处理和表达评估的分离肯定是我遇到的问题,因为我只是有一种不好的感觉,即存在一种情况,副作用的值取决于表达的结果,在这种情况下,副作用必须在完成前处理。表达式的估计。副作用只是在某个地方修改一个内存块。如果相同的表达式在内存块上操作呢?不过,我想不出任何现成的例子能做到这一点。
- @罗伯特戴利:这就是重点:如果编写一个表达式,其中该表达式计算的值基于未正确排序的副作用,则会得到未定义的行为。它是一个两层的系统:您需要运算符优先级,但是源值需要按照先前表达式的任何副作用排序。
- @罗伯特:"如果相同的表达式在那块内存上运行呢?"我想这就是重点。C和C++(对于更坏的情况)不定义副作用发生的顺序,但是他们说当这些副作用必须完成时(在C++ 11之前的下一个顺序点,在下一个"在C++ 11中的操作之后进行排序")。因此,规则"不要在两个序列点之间修改两次内容",以防止含糊不清。
- 为什么不定义呢?这就是我的观点。a = a++,为什么不要求在评估达到这一点后立即按需处理副作用呢?将a修改为等于1,然后返回0,最后将0赋给a。我找不到任何理由不定义这种行为。当他们在标准中这样设置时,他们在想什么?
- @罗伯特戴利:它是如何被定义的行为是一个不同于它为什么是的问题。后者本质上是推测性的,可能与赋予编纂者合理的行动自由和使他们的工作更容易有关。或者这可能是一些历史问题。
- @罗伯特戴利:或者也许规范编写者足够聪明,知道认可像a = a++ + ++a这样的代码是一个可怕的想法;)
- @尼古拉斯"可怕的想法",这是你的个人观点吗?事实在哪里?:)我想不出一个用例会让它变得可怕,或者具有破坏性,或者其他什么。但是,在可读性方面很糟糕,而且这样做没有多大意义,我同意。
优先级规则指定表达式的计算顺序,但在计算期间不必发生副作用。它们可以在下一个序列点之前的任何时间发生。
在这种情况下,增量的副作用既不在赋值之前也不在赋值之后排序,因此表达式具有未定义的行为。
这可能是一个过于简单的解释,但我认为这是因为没有办法解决什么时候用"a"完成了代码。是在增量之后完成的,还是在分配之后?最终决议是圆形的。增量后的赋值更改了应用增量值时的语义。也就是说,代码在"a"递增之前不使用"a",但在分配之后才使用"a"。这几乎是死锁的语言版本。
正如我所说,我相信这不是一个很好的"学术"解释,但这就是我如何把它藏在自己的耳朵里。希望能有所帮助。
让我分析一下a = a++语句中的基本问题。我们希望实现以下所有目标:
确定值a(a++的返回值,1)
递增a(a++的副作用,2)
将旧值分配给a(分配的效力,3)
有两种可能的排序方法:
将原来的a存放到a中(无操作),然后增加a。同a = a; ++a;。这是序列1-3-2。
评估a,增加a,将原值赋回a。同b = a; ++a; a = b;。这是序列1-2-3。
由于没有规定的顺序,因此允许进行任何一种操作。但是他们有不同的最终结果。两个序列都不比另一个更自然。
该声明有两个结果和两个任务:
(因为它是一个后增量)和
这些分配显然会导致a的最终值不同。
C标准的起草者没有指出两个任务中的哪一个应该首先写入a,哪一个应该写入a,因此编译器编写者可以在任何给定的情况下自由选择他们喜欢的内容。
结果是,它(这个特定的语句)不会崩溃,但您的程序不能再依赖于具有特定值的。
这里的要点是,在某些CPU体系结构(如IntelItanium)上,这两个操作可以由编译器在指令级别上并行处理,但要使您的构造定义良好,这是不允许的。在序列点规范的时候,这些体系结构大多是假设的,而且由于Itanium是一种失败,所以可以很好地证明,到2012年,这其中大部分都是语言中不必要的复杂性。基本上,任何仍在使用中的体系结构都没有可能的缺点——即使对于Itanium来说,性能优势也是微乎其微的,编写一个甚至可以利用它的编译器的头疼是巨大的。
还要注意的是,在C++ 11中,序列点被替换为之前的排序和之后的排序,这使得更多的情况像这样定义良好。
- 即使在现代机器上,一个可能的问题是,如果a和b是标识同一int的引用,而x和y是int变量,那么类似a=b++; x=a; ...; y=b;的代码可以被评估为temp=b; a=temp; b=temp+1; x=temp; ... y=a;变量,其行为就好像a在分配给edo之间是自发变化的。cx1〔7〕和y。我希望看到该标准定义了一个执行模型,它可以识别具有传染性的非确定性,但仍停留在轨道上,因为允许任意行为带来的边际收益很小,但是限制这些行为是有用的……
- …如果代码停留在轨道上,那么在某些边缘情况下计算哪些值并不重要时,重叠分配的后果。即使在这些情况下,要求程序员防范这样的分配也会使代码效率降低,而不是让它们产生任意的结果。