8
int func(int **a)
{
    *a = NULL;
    return 1234;
}

int main()
{
    int x = 0, *ptr = &x;
    *ptr = func(&ptr);      // <-???
    printf("%d\n", x);      // print '1234'
    printf("%p\n", ptr);    // print 'nil'

    return 0;
}

Is this an example of undefined behavior or has to do with sequence points? why the line:

*ptr = func(&ptr);

doesn't behave like:

*NULL = 1234;

EDIT: I forgot to mention that I get the output '1234' and 'nil' with gcc 4.7.

hello_hell
  • 154
  • 1
  • 8
  • 1
    If you ever do this in 'real' (production) code: go stand in a corner and think about what you're doing with your life. If it's just theorising: maybe the compiler is being generous? Though this is most probably indeed undefined behaviour. – Kninnug Jun 14 '13 at 19:04
  • It's funny how everyone is talking about the assignment operator and not the function call which is the more important distinction in this case. – Šimon Tóth Jun 14 '13 at 19:18
  • 1
    You might find the information at [In C99, is `f() + g()` undefined or merely unspecified](http://stackoverflow.com/questions/3951017/in-c99-is-fg-undefined-or-merely-unspecified/3951189#3951189) of some help. – Jonathan Leffler Jun 14 '13 at 19:18

5 Answers5

6

Since there is no sequence point between evaluations of the left and right hand sides of the assignment operator, it is not specified whether *ptr or func(&ptr) is evaluated first. Thus it is not guaranteed that the evaluation of *ptr is allowed, and the program has undefined behaviour.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
5

This is undefined behaviour, I believe. The standard does not stipulate when the LHS of the assignment is evaluated compared to the RHS. If *ptr is evaluated after the function is called, you will be dereferencing a null pointer; if it is evaluated before the function is called, then you get sane behaviour.

The code is thoroughly disreputable. Do not try using it, or anything similar, in real code.

Note that there is a sequence point immediately before a function is called, after its arguments have been evaluated; there is also a sequence point immediately before a function returns. Thus, there are sequence points related to the evaluation of the function arguments and its return value, but...and this is crucial in this context...it still does not tell you whether *ptr is evaluated before or after the function is called. Either is possible; both are correct; the code depends on which happens, which makes it rely on undefined behaviour.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
5

The language does not guarantee you that the right-hand side subexpression func(&ptr) in

*ptr = func(&ptr);

is evaluated first, and the left-hand side subexpression *ptr is evaluated later (which is apparently what you expected to happen). The left-hand side can legally be evaluated first, before call to func. And this is exactly what happened in your case: *ptr got evaluated before the call, when ptr was still pointing to x. After that the assignment destination became finalized (i.e. it became known that the code will assign to x). Once it happens, changing ptr no longer changes the assignment destination.

So, the immediate behavior of your code is unspecified due to unspecified order of evaluation. However, one possible evaluation schedule leads to undefined behavior by causing a null pointer dereference. This means that in general case the behavior is undefined.

If I had to model the behavior of this code in terms of C++ language, I'd say that the process of evaluation in this case can be split into these essential steps

1a. int &lhs = *ptr;      // evaluate the left-hand side
1b. int rhs = func(&ptr); // evaluate the right-hand side
2.  lhs = rhs;            // perform the actual assignment

(Even though C language does not have references, internally it uses the same concept of "run-time bound lvalue" to store the result of evaluation of left-hand side of assignment.) The language specification allows enough freedom to make steps 1a and 1b to occur in any order. You expected 1b to occur first, while your compiler decided to start with 1a.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
4

The assignment operator is not a sequence point. So, there is no guarantee, as to which side will be evaluated first. So, it is unspecified behaviour.

In one of the cases (dereferencing a NULLPTR) it could exhibit undefined behavior.

Between consecutive "sequence points" an object's value can be modified only once by an expression.

You can see a list of what are defined sequence points in C here.

Community
  • 1
  • 1
Anirudh Ramanathan
  • 46,179
  • 22
  • 132
  • 191
  • What does the second part of your answer refer to specifically? You emphasized the "more than once" part. But I don't see any potential violations of this specific rule in the above code. Everything is modified only once, and on top of that modification of `ptr` inside `func` is enveloped with sequence points. – AnT stands with Russia Jun 14 '13 at 20:02
  • @AndreyT OP is modifying `ptr` (which would also modify *ptr) on the right, and there is `*ptr` on the left which depends on it. But I see what you mean. I saw a parallel to cases of the type `arr[i] = i++`. I'm not sure how to rephrase this though :( – Anirudh Ramanathan Jun 14 '13 at 20:10
  • 1
    Firstly, modifying `ptr` does not qualify as modifying `*ptr`. That is not what is meant by "modifying" in the standard text. Secondly, everything that happens inside the function is surrounded by sequence points. It does not conflict with anything that happens outside the function. `arr[i] = i++` is indeed UB, but if you wrap `i++` into a function, the behavior will immediately become *unspecified*, not undefined. – AnT stands with Russia Jun 14 '13 at 20:26
1

While the call of a function is a sequence point, this is bound to the evaluation of parameters (before the call), not the functions side-effects (the call itself).

Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151