chux
chux

Reputation: 153547

Is `free(a_comparable_pointer)` well defined or UB?

Certainly allocating and freeing the same pointer is well defined ...

void *p1 = malloc(42);
assert(p1);
...
free(p1);

... and converting though intptr_t/uintptr_t creates a comparable pointer (as in "compare equal" C11 7.20.1.4) when these integer types exist, although not necessarily the same bit pattern. One could say p2 and p3 have the same value, but may have differing representations.

void *p2 = malloc(42);
assert(p2);
uintptr_t u2 = (uintptr_t) p2;
...
void *p3 = (void *) u2;

// Specified to evaluate true C11 7.20.1.4
if (p2 == p3) ...

// Maybe true, maybe false
if (memcmp(&p2, &p3, sizeof p2) == 0) ...

// Note - early version of post had this errant code
// if (memset(&p2, &p3, sizeof p2) == 0) ...

Now to free() via a comparable pointer.

The free function causes the space pointed to by ptr to be deallocated, ... if the argument does not match a pointer earlier returned by a memory management function ... behavior is undefined. C11dr §7.22.3.3 3

void *p4 = malloc(42);
assert(p4);
uintptr_t u4 = (uintptr_t) p4;
...
void *p5 = (void *) u4;
...
free(p5);  // UB or not UB?

So the title question appears to come done to:
Is "comparable" sufficient to "match" concerning free()?

I suspect free(p5) is undefined behavior (UB), but am not certain. No particular application in mind - just trying to understand the corners of C, no rush.

Upvotes: 4

Views: 101

Answers (5)

Keith Thompson
Keith Thompson

Reputation: 263337

References are to N1570, the latest publicly available draft of the 2011 ISO C standard (C11).

void *p4 = malloc(42);
assert(p4);
uintptr_t u4 = (uintptr_t) p4;
...
void *p5 = (void *) u4;
...
free(p5);  // UB or not UB?

The definition of uintptr_t guarantees that the pointer values p4 and p5 compare equal; more tersely, p4 == p5. From the definition of == for pointers in 6.5.9p6, we know that p4 and p5 point to the same object (since we've already established that the value of p3 is not a null pointer).

This doesn't guarantee that they have the same representation. The standard says very little about the representation of pointers (except that they have a representation), so it's entirely possible that p4 and p5 have different representations. The standard explicitly permits this in 6.2.6.1p4:

Two values (other than NaNs) with the same object representation compare equal, but values that compare equal may have different object representations.

(That may or may not actually be possible, depending on the implementation.)

Now the question is, what exactly is passed to free?

Function calls are described in 6.5.2.2. Paragraph 4 says:

In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument.

(Emphasis added.)

So what free() sees is not the object representation of p5 (which might differ from the object representation of p4), but the value of p5, which is guaranteed to be the same as the value of p4. It's likely that the mechanism by which that value is passed might involve making a copy of the object representation, but all the standard says is that the value is passed, and free must treat any distinct representations of the same value in the same way.

Upvotes: 4

jamesdlin
jamesdlin

Reputation: 90015

The language about converting to and from uintptr_t uses "compare equal", but note that conversion between void* and T* uses the same phrase:

A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

(section 6.3.2.3/1 from the ISO C99 standard)

If match and compare equal were not synonyms, then by your reasoning the following would also be UB:

T* p = malloc(n);
...
free(p);

and instead would require:

void* p0 = malloc(n);
T* p = p0;
...
free(p0);

Which goes against common sense. Furthermore, examples of malloc usage in the standard and in K&R 2nd edition do not do anything of the sort.

Upvotes: 2

Victor Havin
Victor Havin

Reputation: 1183

In general, any call to free will be fine as long as it points to a valid heap block. The free behavior becomes unpredictable when the argument doesn't point exactly to the beginning of a valid heap block. For example, the code below will compile, but will crash during execution because the modified pointer doesn't point to a valid heap block:

int* pn = (int*)malloc(sizeof(int));
pn += 2;                // Now points beyond the beginning of an allocated heap block
void* pv = (void*)pn;   
free(pv);               // Therefore will crash

If you remove the pn += 2, it will be fine again. The bottom line: it is OK to modify and cast your pointers, but when you free them, you should make sure they point to saomething that was actually allocated and was not already deallocated.

Upvotes: 0

Riley
Riley

Reputation: 698

free doesn't know/care where the return value was store, or what type it later gets converted to (it gets converted back to a void*). Therefore, it doesn't matter what pointer you pass in as long as it points to memory that has been allocated and has not been deallocated.

Upvotes: 1

2501
2501

Reputation: 25753

The type uintptr_t guarantees that a void pointer after the conversion to this type and back, will compare equal to the original pointer1.

If a pointer compares equal to another pointer, then they point to the same object2.

The behavior in the last example is thus defined and you can use that pointer to free the object: free(p5)


1 (Quoted from: ISO/IEC 9899:201x 7.20.1.4 Integer type capable of holding pointers 1)
The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer: uintptr_t

2 (Quoted from: ISO/IEC 9899:201x 6.5.9 Equality operators 6)
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. 109)

Upvotes: 3

Related Questions