realloc question

J

James S. Singleton

If we do

q = realloc(p, 128) ;

and on (successful) return of realloc, q != p, must the block pointed to
by p be explicitly freed, or has realloc freed it already?
 
C

Christopher Benson-Manica

James S. Singleton said:
q = realloc(p, 128) ;
and on (successful) return of realloc, q != p, must the block pointed to
by p be explicitly freed, or has realloc freed it already?

If q is not a null pointer, then realloc() has indeed taken care of
the memory pointed to by p (which may be the memory pointed to by q).
If q is a null pointer, then p must still be explicitly free()'d.
 
K

Keith Thompson

James S. Singleton said:
If we do

q = realloc(p, 128) ;

and on (successful) return of realloc, q != p, must the block pointed to
by p be explicitly freed, or has realloc freed it already?

Assuming p!=NULL, realloc() takes care of freeing the original block
if it allocates a new one. (If it has done so, then the old pointer
value is indeterminate, and attempting to compare it invokes undefined
behavior.)
 
J

Jordan Abel

Assuming p!=NULL, realloc() takes care of freeing the original block
if it allocates a new one. (If it has done so, then the old pointer
value is indeterminate, and attempting to compare it invokes undefined
behavior.)

Even without dereferencing?
 
J

Jordan Abel

Though it may seem counterintuitive, yes.

Can I still look at it through an unsigned char pointer?

i.e. will ((unsigned char *)(&p))[0] still be the same as it was before?
 
K

Keith Thompson

Jordan Abel said:
Even without dereferencing?

Yes.

It's equivalent to this:

int *ptr = malloc(sizeof *ptr);
/* assume malloc() succeeded */
free(ptr);
if (ptr == NULL) { ... }

The reference to the value of ptr after the call to free() invokes
undefined behavior. This isn't likely to crash the program on most
real-world systems.
 
K

Keith Thompson

Jordan Abel said:
Though it may seem counterintuitive, yes.

Can I still look at it through an unsigned char pointer?

i.e. will ((unsigned char *)(&p))[0] still be the same as it was before?

Yes, you can look at it through an unsigned char pointer (the pointer
value is indeterminate; the bytes are not). There's been some dispute
about whether the bytes can change. I don't *think* they can.

The pointer value, which was valid, becomes indeterminate (and
possibly a trap representation), even though the representation
doesn't (necessarily) change.
 
C

Chris Torek

[Given:
char *p, *q;
...
q = realloc(p, 128);
with an initial valid non-NULL p and a non-NULL result in q]

Can I still look at it through an unsigned char pointer?

i.e. will ((unsigned char *)(&p))[0] still be the same as it was before?

The bit pattern in p is unchanged (ignoring possible changes to
padding bits anyway). Thus, in the absence of padding bits (or
if any padding bits that are present are at least consistent), if
we rewrite the above as:

saved_p = p;
q = realloc(p, 128);
if (memcmp(&p, &saved_p, sizeof p))
puts("this is odd");

the output should not occur.

Unfortunately (for comprehensibility anyway), the value represented
by the unchanged bit pattern has gone from "determinate" to
"indeterminate". Luckily we have an example architecture on which
this can actually occur. It is a rare CPU -- the 80x86, which as
we all know is not found in many computers :) -- but it has
something called a "segment table" in which particular segments
can be marked as either "valid" or "invalid". If the underlying
C library's malloc/free/realloc routines fiddle with the segment
table, it is possible for a previously-valid segment to become
invalid:

void *malloc(size_t size) {
int segment, offset;
... do stuff including allocating a segment ...
set_segment_validity(segment, VALID);
...
return make_new_pointer(segment, offset);
}

void free(void *p) {
int segment, offset;
... do stuff including calculating the segment number ...
set_segment_validity(segment, INVALID);
}

and of course:

void *realloc(void *oldptr, size_t newsize) {
size_t oldsize;
size_t minsize;

oldsize = __malloc_internal_magic_fetch_size(oldptr);
minsize = newsize < oldsize ? newsize : oldsize;
newptr = malloc(newsize);
if (newptr == NULL)
return NULL;
memcpy(oldptr, newptr, minsize);
free(oldptr);
return newptr;
}

Now after a successful realloc() (or any free()), simply loading
the pointer value into a <segment:eek:ffset> register pair (such as
ES:SI) causes an immediate fault, because the segment -- which
used to be marked valid -- is now marked invalid.

The memcmp() call works correctly because the (unchanged) bit
patterns are not loaded into segment registers. If our C compiler
also does not use the segment registers for a test like:

if (p != saved_p)

then that code also works; but if our C compiler *does* use the
segment registers for this test, the code fails. The C standard
permits our C compiler to use the segment registers here. (Exercise:
should we do so? Will the code be any faster or slower if we do?
Will we catch important bugs early if we use the segment registers?
If the code will be slower but we will catch important bugs, is
this tradeoff worthwhile? Should we have a compile-time switch
to generate code one way or the other?)
 
J

Jordan Abel

Unfortunately (for comprehensibility anyway), the value represented
by the unchanged bit pattern has gone from "determinate" to
"indeterminate". Luckily we have an example architecture on which
this can actually occur. It is a rare CPU -- the 80x86, which as
we all know is not found in many computers :)

[snip stuff i vaguely understand]
Now after a successful realloc() (or any free()), simply loading
the pointer value into a <segment:eek:ffset> register pair (such as
ES:SI) causes an immediate fault, because the segment -- which
used to be marked valid -- is now marked invalid.

except fortunately, _modern_ x86-based systems only use the [32-bit]
offset as part of the [user-C-program] pointer representation, giving a
flat-ish 32-bit address space [or three], right? or am i mistaken?
then that code also works; but if our C compiler *does* use the
segment registers for this test, the code fails. The C standard
permits our C compiler to use the segment registers here. (Exercise:
should we do so? Will the code be any faster or slower if we do?
Will we catch important bugs early if we use the segment registers?
If the code will be slower but we will catch important bugs, is
this tradeoff worthwhile?
Should we have a compile-time switch to generate code one way or the
other?)

doing so would require maintaining two sets of libraries, at least for
anything for which pointers are used

[including two crt0^Wum...thingy's - don't forget, argv is an array of
pointers]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top