3

I've read this:

...but I'm still not sure how this should behave:

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

Stream::seek() returns *this and seek/read_integer() respectively call fseek/fread on some FILE*.

This should return 0 like this:

  1. stream.seek(4)
  2. stream.read_integer() (at position 4, returns X, stream position advanced to 8)
  3. stream.seek(4)
  4. stream.read_integer() (at position 4, returns Y == X)
  5. X - Y == 0

This worked well for me on gcc, MinGW and MinGW-w64. But when I decided to extend compiler support for MSVC, I discovered that this doesn't work anymore and returns garbage values. Here's what actually happens on MSVC:

  1. stream.seek(4)
  2. stream.seek(4) (again)
  3. stream.read_integer() (at position 4, returns X, stream position advanced to 8)
  4. stream.read_integer() (at position 8, returns Y != X)
  5. X - Y != 0

Is such execution order well defined? If not, how can I protect myself against shooting myself in the foot like this in the future?

(Wrapping the calls with brackets doesn't seem to do anything.)

Community
  • 1
  • 1
rr-
  • 14,303
  • 6
  • 45
  • 67
  • The evaluation of the operands may be interleaved. Protect yourself by not doing unsequenced things that have side effects. (Brackets have no effect on sequencing.) – molbdnilo Dec 20 '15 at 14:17
  • *Stream::seek() returns *this* -- What is the actual prototype of the function? – PaulMcKenzie Dec 20 '15 at 14:18

2 Answers2

2

The internal order of execution within an expression is not defined. Only the obvious behavior of operator precedence is defined.

So, in this case, the compiler is obliged to call stream.seek(4) twice [unless the compiler figures out that it's "the same result either way"] and stream.read_integer() twice. But the order of those calls is undetermined (or whatever the term is in the C++ standard) - in other words, the compiler can order those four calls any way it likes.

Your code would be even more risky if you did something like:

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

since it's not well defined which of the two reads happen in which order now - it could call the second read_integer first (at offset 0) or after the seek and read at offset 8. Nobody knows which, and the compiler may even re-arrange them if you make subtle changes to the code (e.g. it decides to do things in a diffferent order because you added another variable that uses another register -> re-arrange code to use registers better...)

The solution is to introduce intermediate variables:

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... :)

This should be done in every piece of code where the order of execution is important for the correctness of the code - and bear in mind that "side-effects" (such as reading input, writing output, modifying state) are definitely dependent on order of execution.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • Thanks, though that's a bit disappointing. I can't help but wonder why braces don't help, too. – rr- Dec 20 '15 at 14:34
  • Because braces only affect order of precedence, not actual order of evaluation. This in turn is because the language designers don't want to make too strict rules, as that will affect the potential for optimisation and may affect the possibility of implementing the language on some platforms. – Mats Petersson Dec 20 '15 at 15:12
1

It seems like this behaviour is allowed:

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 means "evaluation of left hand side and right hand side can be interleaved" like "evaluate part of left expression, evaluate right expression, evaluate rest of left expression"

Revolver_Ocelot
  • 8,609
  • 3
  • 30
  • 48