Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is value of x*f(x) unspecified if f modifies x?

I've looked at a bunch of questions regarding sequence points, and haven't been able to figure out if the order of evaluation for x*f(x) is guaranteed if f modifies x, and is this different for f(x)*x.

Consider this code:

#include <iostream>

int fx(int &x) {
  x = x + 1;
  return x;
}

int f1(int &x) {
  return fx(x)*x; // Line A
}

int f2(int &x) {
  return x*fx(x); // Line B
}

int main(void) {
  int a = 6, b = 6;
  std::cout << f1(a) << " " << f2(b) << std::endl;
}

This prints 49 42 on g++ 4.8.4 (Ubuntu 14.04).

I'm wondering whether this is guaranteed behavior or unspecified.

Specifically, in this program, fx gets called twice, with x=6 both times, and returns 7 both times. The difference is that Line A computes 7*7 (taking the value of x after fx returns) while Line B computes 6*7 (taking the value of x before fx returns).

Is this guaranteed behavior? If yes, what part of the standard specifies this?

Also: If I change all the functions to use int *x instead of int &x and make corresponding changes to places they're called from, I get C code which has the same issues. Is the answer any different for C?

like image 328
Navin Avatar asked Sep 10 '15 14:09

Navin


People also ask

Is f (-1) = x =-1?

No, not true. If x = -1, there is division by zero, making the fraction undefined. f (-1) is not . Looking a bit ahead from the scope of the OP's problem, as x approaches -1 from the left, you get completely different values from when x approaches -1 from the right.

Why is|X| differentiable for x ≠ 0?

|x| is differentiable for x ≠ 0, but why is it so? A function is said to be differentiable at x=a, if a unique tangent passes through that point. Otherwise, it's not differentiable at x=a.

Is f (x) derivable for all real x?

Therefore f (x) is continuous for all x. Also LHD = RHD at x=0. Hence f (x) is derivable at x=0. Therefore f (x) is derivable for all real x. Is differentiable?

What value of X causes division by zero to be undefined?

Division by zero is undefined, so what value of x causes the fraction to be undefined? In your problem, the denominator is zero when x + 1 = 0, or equivalently, when x = -1. For many problems like this, a rational function will be undefined for any input value that makes the denominator zero.


3 Answers

In terms of evaluation sequence, it is easier to think of x*f(x) as if it was:

operator*(x, f(x));

so that there are no mathematical preconceptions on how multiplication is supposed to work.

As @dan04 helpfully pointed out, the standard says:

Section 1.9.15: “Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced.”

This means that the compiler is free to evaluate these arguments in any order, the sequence point being operator* call. The only guarantee is that before the operator* is called, both arguments have to be evaluated.

In your example, conceptually, you could be certain that at least one of the arguments will be 7, but you cannot be certain that both of them will. To me, this would be enough to label this behaviour as undefined; however, @user2079303 answer explains well why it is not technically the case.

Regardless of whether the behaviour is undefined or indeterminate, you cannot use such an expression in a well-behaved program.

like image 93
Maksim Solovjov Avatar answered Oct 12 '22 02:10

Maksim Solovjov


The evaluation order of arguments is not specified by the standard, so the behaviour that you see is not guaranteed.

Since you mention sequence points, I'll consider the c++03 standard which uses that term while the later standards have changed wording and abandoned the term.

ISO/IEC 14882:2003(E) §5 /4:

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified...


There is also discussion on whether this is undefined behaviour or is the order merely unspecified. The rest of that paragraph sheds some light (or doubt) on that.

ISO/IEC 14882:2003(E) §5 /4:

... Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined.

x is indeed modified in f and it's value is read as an operand in the same expression where f is called. And it's not specified whether x reads the modified or non-modified value. That might scream Undefined Behaviour! to you, but hold your horses, because the standard also states:

ISO/IEC 14882:2003(E) §1.9 /17:

... When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function 11) ...

So, if f(x) is evaluated first, then there is a sequence point after copying the returned value. So the above rule about UB does not apply because the read of x is not between the next and previous sequence point. The x operand will have the modified value.

If x is evaluated first, then there is a sequence point after evaluating the arguments of f(x) Again, the rule about UB does not apply. In this case x operand will have the non-modified value.

In summary, the order is unspecified but there is no undefined behaviour. It's a bug, but the outcome is predictable to some degree. The behaviour is the same in the later standards, even though the wording changed. I'll not delve into those since it's already covered well in other good answers.


Since you ask about similar situation in C

C89 (draft) 3.3/3:

Except as indicated by the syntax 27 or otherwise specified later (for the function-call operator () , && , || , ?: , and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.

The function call exception is already mentioned here. Following is the paragraph that implies the undefined behaviour if there were no sequence points:

C89 (draft) 3.3/2:

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.26

And here are the sequence points defined:

C89 (draft) A.2

The following are the sequence points described in 2.1.2.3

  • The call to a function, after the arguments have been evaluated (3.3.2.2).

  • ...

  • ... the expression in a return statement (3.6.6.4).

The conclusions are the same as in C++.

like image 12
eerorika Avatar answered Oct 12 '22 02:10

eerorika


A quick note on something I don't see covered explicitly by the other answers:

if the order of evaluation for x*f(x) is guaranteed if f modifies x, and is this different for f(x)*x.

Consider, as in Maksim's answer

operator*(x, f(x));

now there are only two ways of evaluating both arguments before the call as required:

auto lhs = x;        // or auto rhs = f(x);
auto rhs = f(x);     // or auto lhs = x;
    return lhs * rhs

So, when you ask

I'm wondering whether this is guaranteed behavior or unspecified.

the standard doesn't specify which of those two behaviours the compiler must choose, but it does specify those are the only valid behaviours.

So, it's neither guaranteed nor entirely unspecified.


Oh, and:

I've looked at a bunch of questions regarding sequence points, and haven't been able to figure out if the order of evaluation ...

sequence points are a used in the C language standard's treatment of this, but not in the C++ standard.

like image 7
Useless Avatar answered Oct 12 '22 02:10

Useless