重新加载序列点和方法链接

Sequence Points and Method Chaining reloaded

我读过这个:

...但我仍然不确定这应该如何表现:

int should_be_zero
    = stream.seek(4).read_integer()
    - stream.seek(4).read_integer();

Stream::seek() returns *thisseek/read_integer() 分别调用 fseek/fread 一些 FILE*.

这应该 return 0 像这样:

  1. stream.seek(4)
  2. stream.read_integer()(在位置4,returns X,流位置前进到8)
  3. stream.seek(4)
  4. stream.read_integer()(位置4,returns Y == X
  5. X - Y == 0

这对我来说在 gcc、MinGW 和 MinGW-w64 上运行良好。但是当我决定扩展编译器对 MSVC 的支持时,我发现这不再有效并且 returns 垃圾值。这是 MSVC 上实际发生的事情:

  1. stream.seek(4)
  2. stream.seek(4)(再次)
  3. stream.read_integer()(在位置4,returns X,流位置前进到8)
  4. stream.read_integer()(在第8位,returns Y != X
  5. X - Y != 0

这样的执行顺序是否明确?如果没有,我以后怎么保护自己不被这样搬起石头砸自己的脚?

(用括号括起来的调用似乎没有任何作用。)

似乎允许这种行为:

1.9/15
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. —end note ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
[...]
When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ]

Unsequenced 表示 "evaluation of left hand side and right hand side can be interleaved" 类似于 "evaluate part of left expression, evaluate right expression, evaluate rest of left expression"

未定义表达式中的内部执行顺序。仅定义了运算符优先级的明显行为。

因此,在这种情况下,编译器必须调用 stream.seek(4) 两次 [除非编译器发现它是 "the same result either way"] 和 stream.read_integer() 两次。但是这些调用的顺序是不确定的(或者 C++ 标准中的任何术语)——换句话说,编译器可以按照它喜欢的任何方式对这四个调用进行排序。

如果您执行以下操作,您的代码将更加危险:

 int x
    = stream.seek(4).read_integer()
    - stream.read_integer();  

因为现在没有明确定义两次读取中的哪一次以什么顺序发生 - 它可以首先调用第二个 read_integer(在偏移量 0 处)或在查找和读取之后在偏移量 8 处调用。没有人知道哪个,如果您对代码进行细微更改,编译器甚至可能会重新安排它们(例如,它决定以不同的顺序执行操作,因为您添加了另一个使用另一个寄存器的变量 -> 重新安排代码以更好地使用寄存器.. .)

解决方法是引入中间变量:

int a = stream.seek(4).read_integer();
int b = stream.seek(4).read_integer();

int should_be_zero = a - b; // Or b - a, if that's what you want... :)

在执行顺序对代码的正确性很重要的每一段代码中都应该这样做 - 请记住 "side-effects"(例如读取输入、写入输出、修改状态)绝对取决于执行顺序。