我读过这个:
- Undefined behavior and sequence points
- Undefined behavior and sequence points reloaded
- Sequence Points and Method Chaining
- GCC bug? Chaining methods, broken sequence point
...但我仍然不确定这应该如何表现:
int should_be_zero
= stream.seek(4).read_integer()
- stream.seek(4).read_integer();
Stream::seek()
返回*this
和seek
/read_integer()
分别调用fseek
/fread
在一些 FILE*
上。
这应该像这样返回 0:
stream.seek(4)
stream.read_integer()
(在位置4,返回X
,流位置前进到8)stream.seek(4)
stream.read_integer()
(在位置 4,返回Y == X
)X - Y == 0
这对我来说在 gcc、MinGW 和 MinGW-w64 上运行良好。但是当我决定扩展编译器对 MSVC 的支持时,我发现这不再起作用并返回垃圾值。这是 MSVC 上实际发生的事情:
stream.seek(4)
stream.seek(4)
(再次)stream.read_integer()
(在位置4,返回X
,流位置前进到8)stream.read_integer()
(在位置 8,返回Y != X
)X - Y != 0
这样的执行顺序是否明确?如果没有,我该如何保护自己,以免将来像这样搬起石头砸自己的脚?
(用方括号包裹调用似乎没有任何作用。)
最佳答案
未定义表达式中的内部执行顺序。仅定义了运算符优先级的明显行为。
因此,在这种情况下,编译器必须调用 stream.seek(4)
两次 [除非编译器发现它“无论哪种方式都是相同的结果”] 和 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... :)
在执行顺序对代码的正确性很重要的每一段代码中都应该这样做——并记住“副作用”(例如读取输入、写入输出、修改状态)肯定是取决于执行顺序。
关于c++ - 重新加载序列点和方法链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34381825/