Is it safe to do something like foo(x, &x)?

Viewed 143

Note, I would not write code like this. I'm just curious, and it would help me writing a better answer for another question. But let's say that we have this function:

void foo(int a, int *b)
{
    *b  = 2*a;
}

And call it like this:

int x=42;
foo(x, &x);

Apart from the fact that it is a very strong code smell, can this cause any real problems? Is it UB or does it violate any rules in the C standard?

3 Answers

This

int x=42;
foo(x, &x);

is a well-formed code. The order of the evaluation of arguments is not important in this case.

In fact the function call is equivalent to

foo( 42, &x);

because the first argument is passed by value.

The code is OK, but to dig a bit deeper between the lines to see what special case situations exist, which ones that are fine and which ones that could potentially cause problems:

  • The order of evaluation of function arguments is unspecified and unsequenced, but it does not matter in this specific case.

  • Function arguments are evaluated before passed to a function, then there is a sequence point after the evaluation. Meaning that all calculations and side effects in the arguments occur before the function is called (but in an unspecified order in relation to each other).

    So even (artificial crap) code such as this is actually well-defined:

      void foo(int a, int *b)
      {
         printf("%d\n", a); // prints 42
        *b = 2**b;          // gives 43 * 43 = 86
      }
      ...
      int x=42;
      foo(x++, &x);
    

    The x++ vs &x is fine since &x is not a value computation of the object. And thanks to the sequence points, the x++ occurs before the calculation inside the function, so that part is also well-defined.

  • The parameter a inside the function is of course a local copy of caller-side x, so the function can do what it pleases with that one.

  • Had it been two pointers pointing at the same object, then they would "overlap" and that's undefined behavior in some cases, depending on what the function does. For example memcpy(&x, &x, sizeof x); is undefined behavior.

  • There is a sequence point at each ; and also at the end of the function.

  • For variables declared at file scope, the function must assume that pointer parameters modifying the pointed-at value might modify the file scope variable. So in case of this code:

      void foo(int a, int *b)
      {
        extern int x;
        x = 2*a;
        printf("%d\n", *b);
      }
    

    the compiler must fetch the value of *b after the assignment to x, because it can't assume that b doesn't point at x - the pointer could be an alias of &x. And here's where the various rules of pointer aliasing comes in.

  • Similarly, two pointer parameters of the same type might point at the same object in the caller code and the compiler isn't allowed to assume that they don't, unless we manually add a restrict qualifier to them.

The question is already well answered, but the above would work even without being in a separate function due to evaluation order.

Even the following code would work fine.

void foo(int *a, int *b) {
  *b = 2*(*a);
}

int x = 42;
foo(&x, &x);

and x would contain 84 after it executes. The right-hand side of the assignment is evaluated before the left-hand side (that's why we can do x = x+1; and drive mathematicians crazy :) )

Related