[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
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?)