1

Consider this example:

T *fun(T *x) {
    // do something with contents of (*x), do not change x itself
    return x;
}


T var = ...;
var = *fun(&var);  // <- is this valid?

The highlighted line ultimately takes a pointer to a variable, dereferences it, and copies the contents of the memory pointed to by the pointer into the same location

Is this allowed? If this trips UB, why? Can this be considered an example of aliasing?

Alex
  • 1,165
  • 2
  • 9
  • 27

3 Answers3

1

This is fine. The expression has a side effect of updating var, but this happens after evaluating the value of var on the right side since the right side has to be evaluated before var can be updated.

What you're doing is effectively the same as:

var = var;

It would also be fine if func modified *x because a function call introduces a sequence point.

Expressions such as this are mentioned in section 6.5p2 of the C standard:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.84)

84)) This paragraph renders undefined statement expressions such as

i = ++i + 1;
a[i++] = i;

while allowing

i=i+1;
a[i] = i
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Re “because a function call introduces a sequence point”: That is not why it is okay for `func` to modify `*x` in `var = *func(&var);`. The sequence point introduced by a function call is before the function call, so it is not between a modification in the execution of `func` and the assignment to `var`. It is okay because the function must return a value with a `return` statement with an expression, and that is a full expression, so there is a sequence point between it and any prior full expression in the function. – Eric Postpischil May 22 '21 at 20:07
  • If that `return` expression modifies `*x`, I do not see any intervening sequence point. – Eric Postpischil May 22 '21 at 20:07
  • @EricPostpischil: You're thinking of something like `return x + (*x)++;`? (Let's assume `*x` was 0 so that the return value is still `&var`.) – Nate Eldredge May 22 '21 at 21:19
  • @NateEldredge: Yes, or simply `return *x = 0;`. Is the behavior of `int f(int *x) { return *x = 0; } int main(void) { int a = 1; a = f(&a); }` defined? – Eric Postpischil May 22 '21 at 21:50
  • @NateEldredge: The behavior is not defined; I [answered this issue eight years ago](https://stackoverflow.com/a/15637783/298225)! – Eric Postpischil May 26 '21 at 01:07
0

Yes it's valid the new value of var is only assigned after the right side of the expression is completely eveluated.

0

C compilers are generally required to allow for the possibility that an lvalue on the right side of an assignment operator might identify the same object an lvalue on the left which has the same type and is accessed in similar fashion. They are not generally required to allow for the possibility that an lvalue used on the right side of an expression might interact with one on the left side via other means. Among other things, given something like:

void doSomething(long *p, long *q, long *r)
{
  *r = *p + *q;
}

a compiler could load the bottom half of *p, add the bottom half of *q, store the result to the bottom half of *r, and then load the top half of *p, add the top half *q along with any carry, and store the result to the top half of *r. This could malfunction if e.g. the bottom word of *r occupied the same storage as the top word of *q, even if code would never try to read *q after the assignment was processed.

If a compiler has no reason to think that an assignment will modify any of the lvalues used in the source, it may perform additional optimizations. For example, if a compiler where _Bool is one byte is given something like:

int x;
void handleAssign(char *p)
{
  x += p[0]*p[1] + p[2]*p[3];
}

it would be entitled to process it as though it were written:

int temp;
void handleAssign(char *p)
{
  x += p[0]*p[1]; // Using wrapping two's-complement math
  x += p[2]*p[3]; // Using wrapping two's-complement math
}

which might improve efficiency on platforms that could directly add the result of the multiply to a memory operand but would otherwise have to transfer it to another register, add the result of the second multiply to that register, and then add that result to x. Such processing is separate from type-based aliasing.

supercat
  • 77,689
  • 9
  • 166
  • 211