views:

219

answers:

4

I have the following code

int m[4]={1,2,3,4}, *y; 
y=m; 
*y = f(y++); // Expression A

My friend told me that Expression A has a well defined behavior but I am not sure whether he is correct.

According to him function f() introduces a sequence point in between and hence the behavior is well defined.

Someone please clarify.

P.S: I know we should not write such code for practical purpose. It is just for the purpose of learning. :)

A: 

EDIT: this is incorrect, however I'm leaving it here because the discussion that follows in the comments is somewhat illuminating, and I hope valuable.

It's well-defined based on the evaluation order of operators in C (or C++).

Assignment forces evaluation of the right hand side of the expression first. Function application forces evaluation of its arguments first, so the effect seems reasonably clear (although I haven't tried running it, so feel free to correct me!). We can rewrite this using temporary variables (I'll call them t0 and t1), and I believe this might be a little clearer:

t0 = y++;
t1 = f(t0);
*y = t1;

The term "sequence point" is a bit of a red herring. A sequence point isn't really created, rather it's just the consequence of having a strict evaluation order defined for the language.

EDIT: While this answer seems intellectually satisfying, James McNellis's answer quotes the relevant piece of the C99 spec that states that the evaluation order of assignment is not well-defined. Full credit to him for actually checking his facts. I'm going to revise my answer from "it's well-defined" to "it's probably well-defined with respect to a particular compiler", as I think it's unlikely that most compilers would regularly change the order in which they emit such code (I say "probably" to account for any very aggressive optimisation).

Gian
_Assignment forces evaluation of the right hand side of the expression first._ This is not true: the order of evaluation is unspecified.
James McNellis
Where does it state that the right hand side of an assignment must be evaluated first?
jalf
Thanks, I just edited my answer to account for that, and thanks for teaching me something new about C :)
Gian
@jalf, I've spent a lot of time in the strict functional world, and I (incorrectly) assumed that the same interpretation made sense wrt C.
Gian
@Gian: How come? In a strict functional language, you don't *have* assignments, so they can't have a fixed evaluation order.
jalf
I'm pretty sure this is wrong. You need a third "temporary variable" for the destination of the indirect write (through `y`) and I see no reason why it has to be evaluated after the `y++` and not before...
R..
You are missing one operation (see below).
Martin York
@jalf, You have value bindings, and some languages (e.g. SML and OCaml) have mutable assignment too.
Gian
@R.., Yes, the comments prior to yours identified the problem. If we assume my original (incorrect) assumption that assignment was necessarily evaluated right-to-left, then the example is fine. The edit in the post clarifies that it is indeed not correct, and additional temporaries would not fix it.
Gian
+13  A: 

At best, the code in question has unspecified behavior. For the assignment operators, "the order of evaluation of the operands is unspecified" (C99 §6.5.16/4).

If the left operand is evaluated first, the result of f(y++) will be stored in m[0]. If the right operand is evaluated first, the result will be stored in m[1].

As for whether the behavior is undefined, the relevant paragraph is:

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 read only to determine the value to be stored (C99 §6.5/2).

If the left side is evaluated first, then we run afoul of the second sentence because the ordering is:

  1. The value of y is read on the left side to dereference it
  2. The value of y is read on the right side to increment it
  3. There is a sequence point after the evaluation of the arguments to the function (so, the side effect of y++ is complete and y is written to)

In step 1, the "prior value" of y is read but for a purpose other than "determining the value to be stored." Thus, the behavior is indeed undefined because one valid evaluation order yields undefined behavior.

James McNellis
+1. I believe it is even undefined, because the evaluations of `*y` and `y++` are unsequenced and the latter induces a side effect on `y`.
avakar
Thanks James, your answer is perfectly fine. AC :-)
Prasoon Saurav
+1  A: 

The expression is not well defined:

A valid interpretation of the expression is:

(1) int* t0 = y++; 
(2) int  t1 = f(t0);
(3) int& t2 = *y;
-----------------
t2 = t1; 

An equally valid interpretation of the expression is:

(1) int& t2 = *y;
(2) int* t0 = y++; 
(3) int  t1 = f(t0);
-----------------
t2 = t1; 

Both of these are vaalid and generate different results. So the expression has undefined result.

Martin York
In general, the fact that an expression can have two different results merely means that the result is *unspecified*. However, in this particular case the result is indeed undefined as the computations of `t0` and `t2` can occur in any order and `t0` has side effects on `y` while `t2` uses the value of `y`.
avakar
+12  A: 

You are absolutely right about function call introducing a sequence point. However, that sequence point does not save the situation in your case.

Consider this simple example first

i = some_function(i++);

Is it valid? Yes, it is. Why? It is valid because the sequence point introduced by the function (the one you are talking about) separates two modifications of i from each other, thus making the code valid. There's no order of evaluation of this expression that would result in i being modified twice without an intervening sequence point.

However, let's return to your variant

*y = f(y++);

In this case that sequence point exists as well. However, the language makes no guarantee about the order of evaluation of = operator (meaning: the language makes no guarantee about which operand of assignment operator is evaluated first: left or right). It is quite allowable for the compiler to evaluate the left-hand side first (*y ), the function argument second (y++), then call the function and then perform the actual assignment. In this potential scenario the first two steps - reading the y and modifying the y - are not separated by a sequence point. Thus, the behavior is undefined.

AndreyT
@Ben Voigt: I was talking about the relative order of evaluation of operands of `=` operator, i.e. whether the LHS or RHS is evaluated first.
AndreyT