hello_hell
hello_hell

Reputation: 154

Assignment and pointers, undefined behavior?

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.

Upvotes: 8

Views: 695

Answers (5)

Anirudh Ramanathan
Anirudh Ramanathan

Reputation: 46728

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.

Upvotes: 4

AnT stands with Russia
AnT stands with Russia

Reputation: 320381

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.

Upvotes: 5

Jonathan Leffler
Jonathan Leffler

Reputation: 753525

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.

Upvotes: 5

Šimon T&#243;th
Šimon T&#243;th

Reputation: 36433

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).

Upvotes: 1

Kerrek SB
Kerrek SB

Reputation: 476950

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.

Upvotes: 6

Related Questions