Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid pointer becoming valid again

int *p;
{
    int x = 0;
    p = &x;
}
// p is no longer valid
{
    int x = 0;
    if (&x == p) {
        *p = 2;  // Is this valid?
    }
}

Accessing a pointer after the thing it points to has been freed is undefined behavior, but what happens if some later allocation happens in the same area, and you explicitly compare the old pointer to a pointer to the new thing? Would it have mattered if I cast &x and p to uintptr_t before comparing them?

(I know it's not guaranteed that the two x variables occupy the same spot. I have no reason to do this, but I can imagine, say, an algorithm where you intersect a set of pointers that might have been freed with a set of definitely valid pointers, removing the invalid pointers in the process. If a previously-invalidated pointer is equal to a known good pointer, I'm curious what would happen.)

like image 523
user2357112 supports Monica Avatar asked Aug 22 '13 14:08

user2357112 supports Monica


3 Answers

By my understanding of the standard (6.2.4. (2))

The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

you have undefined behaviour when you compare

if (&x == p) {

as that meets these points listed in Annex J.2:

— The value of a pointer to an object whose lifetime has ended is used (6.2.4).
— The value of an object with automatic storage duration is used while it is indeterminate (6.2.4, 6.7.9, 6.8).

like image 139
Daniel Fischer Avatar answered Dec 31 '22 01:12

Daniel Fischer


Okay, this seems to be interpreted as a two- make that three part question by some people.

First, there were concerns if using the pointer for a comparison is defined at all.

As is pointed out in the comments, the mere use of the pointer is UB, since $J.2: says use of pointer to object whose lifetime has ended is UB.

However, if that obstacle is passed (which is well in the range of UB, it can work after all and will on many platforms), here is what I found about the other concerns:

Given the pointers do compare equal, the code is valid:

C Standard, §6.5.3.2,4:

[...] If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

Although a footnote at that location explicitly says. that the address of an object after the end of its lifetime is an invalid pointer value, this does not apply here, since the if makes sure the pointer's value is the address of x and thus is valid.

C++ Standard, §3.9.2,3:

If an object of type T is located at an address A, a pointer of type cv T* whose value is the address A is said to point to that object, regardless of how the value was obtained. [ Note: For instance, the address one past the end of an array (5.7) would be considered to point to an unrelated object of the array’s element type that might be located at that address.

Emphasis is mine.

like image 45
Arne Mertz Avatar answered Dec 31 '22 02:12

Arne Mertz


It will probably work with most of the compilers but it still is undefined behavior. For the C language these x are two different objects, one has ended its lifetime, so you have UB.

More seriously, some compilers may decide to fool you in a different way than you expect.

The C standard says

Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.

Note in particular the phrase "both are pointers to the same object". In the sense of the standard the two "x"s are not the same object. They may happen to be realized in the same memory location, but this is to the discretion of the compiler. Since they are clearly two distinct objects, declared in different scopes the comparison should in fact never be true. So an optimizer might well cut away that branch completely.

Another aspect that has not yet been discussed of all that is that the validity of this depends on the "lifetime" of the objects and not the scope. If you'd add a possible jump into that scope

{
    int x = 0;
    p = &x;
  BLURB: ;
}
...
if (...)
...
if (something) goto BLURB;

the lifetime would extend as long as the scope of the first x is reachable. Then everything is valid behavior, but still your test would always be false, and optimized out by a decent compiler.

From all that you see that you better leave it at argument for UB, and don't play such games in real code.

like image 35
Jens Gustedt Avatar answered Dec 31 '22 02:12

Jens Gustedt