How to know the memory pointed by a ptr is freed?

C

Chris Torek

True enough; but there must be a function version as well:

void (*fp)(void *);
...
fp = input_value == 42 ? free : zorg;
fp(ptr);

In the end, it is "too difficult" (and pointless, if you will pardon
the wording :) ) for free() to change the bits stored in an object
passed to it.

If anybody looks at the standard to learn how to call free(),
they *should* be able to count on the value (i.e. the bit pattern)
of "ptr" not changing. (Why they would want to, I can't say).

Here is the real problem -- the disconnect: the *bit pattern* and
the *value* are not the same thing!

Those who have written assembly code, or investigated thier C
implementations far enough, are already familiar with this with
respect to floating-point numbers. Typically "int" or "long"
is 4 or 8 bytes, and "float" or "double" is also 4 or 8 bytes
-- but a 4-byte "int" that represents (say) 12345 is *not*
also 12345.0f, not at all.

C99 makes it clear that there is a "representation" (as stored
in an object) for some particular value. The representation is
the bit-pattern in memory, and using "unsigned char *" pointers,
you can always inspect the representation of any addressable
object:

#include <stdio.h>

void show(const char *, void *, size_t);

int main(void) {
int i = 12345;
float f = 12345.0;

show("int i = 12345 ", &i, sizeof i);
show("float f = 12345.0", &f, sizeof f);
return 0;
}

void show(const char *prefix, void *mem, size_t len) {
size_t i;
unsigned char *cp = mem;

printf("%s:", prefix);
for (i = 0; i < len; i++)
printf(" %2.2x", (unsigned int)cp);
printf("\n");
}

% cc -o t -O -ansi -pedantic -W -Wall t.c
% ./t
int i = 12345 : 39 30 00 00
float f = 12345.0: 00 e4 40 46
%

Now, the trick to free() is that, while it is virtually certain
not to change the bit pattern -- the *representation* of some value
-- what it *can* do is change the *meaning* of those bits. Just
as (in this case) the bits "39 30 00 00" "mean" 12345 when they
represent an int, we need a completely different set of bytes to
"mean" 12345.0 as a float. The free() function can, in effect,
change the way that a pointer is interpreted.

Imagine a system just like your typical 32-bit Intel, except that
pointers use *eight* bytes. Half the pointer is used as an index
into a table. When table is set to 0, the pointer is considered
invalid, but when table is set nonzero, the pointer is valid.
The free() function clears table -- so now, even though the
"memory address" half of the pointer is still good, the pointer
itself is invalid.

The pointer is unchanged, but it has gone from "valid" to "invalid".
The *representation* is unchanged, but the *value* is different.
 
K

Keith Thompson

RCollins said:
Keith Thompson wrote: [...]
I dunno ... the standard shows the definition of free() as
void free(void *ptr)

If anybody looks at the standard to learn how to call free(),
they *should* be able to count on the value (i.e. the bit pattern)
of "ptr" not changing. (Why they would want to, I can't say).

The object ptr can be viewed in more than one way. After a call to
free, attempting to view it as a pointer value invokes undefined
behavior -- but you can still legally view it (as you can view any
object) as an array of unsigned char.

[...]
Ummm... what do you mean by "indeterminate"?

An indeterminate value is either an unspecified value or a trap
representation. You'd have to follow the maze of twisty definitions
in section 3 of the standard to understand precisely what unspecified
values and trap representations are, but basically it means that using
the value can invoke undefined behavior.

A "value" is the "precise meaning of the contents of an object when
interpreted as having a specific type". So the value of ptr when
viewed as a pointer is different from the value of ptr when viewed as
an array of unsigned char -- though both values have the same
representation. (Very roughly, value == representation + type.)
I can do a memcpy()
of the bits in "p" to an array of unsigned char, and determine
*exactly* what the value is (or, simpler yet, since I know that
free() did not change "p", I can simply print it out before the
call to free() ... it will be the same after the call).

memcpy() doesn't use the value of ptr *as a pointer*, so it doesn't
invoke undefined behavior.
Or do we mean that I cannot 'see' the bits in "p" at all? That sounds
like a trap representation to me ... and traps are very deterministic
(there's only a limited number of them).

You can see the bits; you just can't see the pointer.

BTW, referring to a trap representation doesn't necessarily cause a
trap; undefined behavior can include behaving just as you'd expect it
to.
Or do we mean that I shouldn't be 'touching' the memory returned to
the system by free(), because we cannot determine the state of that
memory after the free() call?

It's also the case that you shouldn't touch the memory to which ptr
had pointed before the call to free() -- but the interesting thing is
that you shouldn't touch the value of ptr itself, even if you don't
dereference it.
I agree; I think the whole issue boils down to a very convoluted and
complex warning about trying to access free()'d memory. (i.e., don't
do it!)

Don't try to access free()'d memory *or* the value of the pointer that
pointed to it. The free()'d memory should be treated as if it no
longer exists; even an attempt to view it as an array of unsigned char
invokes undefined behavior. The pointer value is still there, sitting
in an object that you declared yourself, tempting you, begging you,
softly whispering "go ahead, access me, evaluate me, you know you're
curious, what could go wrong?", all the while getting the demons lined
up and ready to fly out your nose. (It's late; I *really* need to go
home.)
 
R

Ravi Uday

Keith Thompson said:
RCollins said:
Keith Thompson wrote: [...]
I dunno ... the standard shows the definition of free() as
void free(void *ptr)

If anybody looks at the standard to learn how to call free(),
they *should* be able to count on the value (i.e. the bit pattern)
of "ptr" not changing. (Why they would want to, I can't say).

The object ptr can be viewed in more than one way. After a call to
free, attempting to view it as a pointer value invokes undefined
behavior -- but you can still legally view it (as you can view any
object) as an array of unsigned char.

[...]
Ummm... what do you mean by "indeterminate"?

An indeterminate value is either an unspecified value or a trap
representation. You'd have to follow the maze of twisty definitions
in section 3 of the standard to understand precisely what unspecified
values and trap representations are, but basically it means that using
the value can invoke undefined behavior.

A "value" is the "precise meaning of the contents of an object when
interpreted as having a specific type". So the value of ptr when
viewed as a pointer is different from the value of ptr when viewed as
an array of unsigned char -- though both values have the same
representation. (Very roughly, value == representation + type.)
I can do a memcpy()
of the bits in "p" to an array of unsigned char, and determine
*exactly* what the value is (or, simpler yet, since I know that
free() did not change "p", I can simply print it out before the
call to free() ... it will be the same after the call).

memcpy() doesn't use the value of ptr *as a pointer*, so it doesn't
invoke undefined behavior.
Or do we mean that I cannot 'see' the bits in "p" at all? That sounds
like a trap representation to me ... and traps are very deterministic
(there's only a limited number of them).

You can see the bits; you just can't see the pointer.
Thats what the OP wanted, to know whether the pointer is valid.
I think the system/OS can fill the first word in the header of a freed
block with some
bit pattern (0xdeadbeef) before returning the call (free()) to the user
So seeing the memory dump of the pointer might be of clue.

Of-course you shouldnt de-reference the pointer once the free() returns !
BTW, referring to a trap representation doesn't necessarily cause a
trap; undefined behavior can include behaving just as you'd expect it
to.


It's also the case that you shouldn't touch the memory to which ptr
had pointed before the call to free() -- but the interesting thing is
that you shouldn't touch the value of ptr itself, even if you don't
dereference it.


Don't try to access free()'d memory *or* the value of the pointer that
pointed to it. The free()'d memory should be treated as if it no
longer exists; even an attempt to view it as an array of unsigned char
invokes undefined behavior. The pointer value is still there, sitting
in an object that you declared yourself, tempting you, begging you,
softly whispering "go ahead, access me, evaluate me, you know you're
curious, what could go wrong?", all the while getting the demons lined
up and ready to fly out your nose. (It's late; I *really* need to go
home.)
 
J

Jens.Toerring

Jack Klein wrote:
I don't know ... the standard doesn't mention them. All I know is that
the prototype for free() doesn't _appear_ to allow the value of "ptr"
to be changed. (Ignoring the possibility of behind-the-scenes
'compiler magic'). As for those 37 copies ... I'd say it was up to
the programmer to keep them straightened out properly.
While in a practical sense we all know what it means (or we should,
if we're going to make a living at this stuff), in another sense it
is somewhat ambiguous. After a call to free(), the value of "ptr"
is indeterminate. Does that mean that there is now a random bit
pattern stored in "ptr", or does it mean that we cannot under any
circumstances 'view' the bit pattern stored in "ptr"?
Of course, in the 'real world', it means neither. It simply means
that we gave that memory back to the system and any access to it
leads to undefined behavior.
Does this mean I can't copy out the bit pattern and view it?
(say, with a memcpy() into an array of unsigned chars). While
a bit pattern representing a trap may have been copied into "ptr",
I don't think free is _allowed_ to do that.
Here is the question it all boils down to:
Given that the standard specifies the prototype of free() to be
void free(void *ptr)
Is then free() *required* to behave that way? By the definition,
the bit pattern of "ptr" should not change; but is 'compiler magic'
allowed to happen here with a prototype that doesn't explicitly
*show* the value has changed?

I think it's not so much "compiler magic" but a question how the
underlying hardware works. Let's assume you have a machine that
has dedicated address registers (like e.g. the 68000 processors
had). Moreover, the OS is allowed to unmap the address from the
virual address space of a process once free() has called on a
pointer. If now the processor also tests if an address it loads
into an address register is mapped into the address space of the
process (with an exception for an address that represents the NULL
pointer) and e.g. segfaults when it isn't than you have a sitation
where none of the bits of the pointer ever changed but taking its
value (by loading it into an address register) isn't possible.

If memcpy()ing the content of that pointer somehwere else is
possible may depend on such a machine on what kind of machine
instructions are used in the process - if the value of the pointer
gets copied into an address register in the process it would
crash the program, if it's going in a data register (or no
register at all) it might work perfectly well.

While no machine is required to work that way it's possible to
write a conforming C compiler such a kind of machine. But
therefore you need some allowance in the standard that says
that "the result taking the value of a free()ed pointer is
indeterminate". Also a lot of other seemingly hard to explain
requirements (like, for example, why, when you have an array
a[10], calculating a+20 is undefined) suddenly make a lot of
sense on such a machine.
Regards, Jens
 
R

RCollins

I think it's not so much "compiler magic" but a question how the
underlying hardware works. Let's assume you have a machine that
has dedicated address registers (like e.g. the 68000 processors
had). Moreover, the OS is allowed to unmap the address from the
virual address space of a process once free() has called on a
pointer. If now the processor also tests if an address it loads
into an address register is mapped into the address space of the
process (with an exception for an address that represents the NULL
pointer) and e.g. segfaults when it isn't than you have a sitation
where none of the bits of the pointer ever changed but taking its
value (by loading it into an address register) isn't possible.

No, this is too deep. Given the standard's definition of free(),
then I should be able to do this:

#include <stdio.h>

int mail(void) {
unsigned long before, after;
int *ptr;

ptr = malloc(100); /* ignore possible errors for now */
before = (unsigned long) ptr;
free(ptr);
after = (unsigned long) ptr;

if (before == after) printf("bits in ptr have not changed\n")
else printf("bits in ptr HAVE changed\n");

return 0;
}

And I should see that the bit representation in "ptr" have not
changed. Regardless of whether they point to a valid memory
location or not.

If memcpy()ing the content of that pointer somehwere else is
possible may depend on such a machine on what kind of machine
instructions are used in the process - if the value of the pointer
gets copied into an address register in the process it would
crash the program, if it's going in a data register (or no
register at all) it might work perfectly well.

Copying the (bits) contents of "ptr" to another location should
*always* be allowed; copying them back into "ptr" is another
question.
While no machine is required to work that way it's possible to
write a conforming C compiler such a kind of machine. But
therefore you need some allowance in the standard that says
that "the result taking the value of a free()ed pointer is
indeterminate". Also a lot of other seemingly hard to explain
requirements (like, for example, why, when you have an array
a[10], calculating a+20 is undefined) suddenly make a lot of
sense on such a machine.
Regards, Jens

Keep in mind the OP's original question: is there any way to know
if the bit representation in "ptr" is a valid memory location?
 
F

Flash Gordon

No, this is too deep. Given the standard's definition of free(),
then I should be able to do this:

#include <stdio.h>

int mail(void) {
unsigned long before, after;
int *ptr;

ptr = malloc(100); /* ignore possible errors for now */

You failed to include stdlib.h which is required for malloc. Your
compiler was required to generate a diagnostic for this line if invoked
in ANSI/ISO mode.

On some REAL implementations thus would cause your program to fail.
before = (unsigned long) ptr;

Implementation defined behaviour. A pointer might require more bits than
a long.
free(ptr);
after = (unsigned long) ptr;

BANG. The pointer might be loaded in to an address register causing the
program to crash. The C standard does not allow you to read the value of
the pointer after you have freed it.
if (before == after) printf("bits in ptr have not changed\n")
else printf("bits in ptr HAVE changed\n");

return 0;
}

And I should see that the bit representation in "ptr" have not
changed. Regardless of whether they point to a valid memory
location or not.

The code you wanted was something like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
int *ptr;
unsigned char before[sizeof ptr];

ptr = malloc(100); /* ignore possible errors for now */
memcpy( before, ptr, sizeof ptr );
free(ptr);

if (memcmp( before, ptr, sizeof ptr) == 0)
printf("bits in ptr have not changed\n")
else
printf("bits in ptr HAVE changed\n");
return 0;
}

Copying the (bits) contents of "ptr" to another location should
*always* be allowed; copying them back into "ptr" is another
question.

Copying the bits is allowed, but casting a pointer to an unsigned long
does not copy the bits it takes the value and applies an implementation
defined translation. If you want to copy bits use memcpy as I did and
copy them in to a large enough array of unsigned char as unsigned char
is guaranteed by the standard to not have any trap values.
While no machine is required to work that way it's possible to
write a conforming C compiler such a kind of machine. But
therefore you need some allowance in the standard that says
that "the result taking the value of a free()ed pointer is
indeterminate". Also a lot of other seemingly hard to explain
requirements (like, for example, why, when you have an array
a[10], calculating a+20 is undefined) suddenly make a lot of
sense on such a machine.
Regards, Jens

Keep in mind the OP's original question: is there any way to know
if the bit representation in "ptr" is a valid memory location?

There is no standard way to find out. The only value that you cannot
dereference that you can portably detect is the null pointer.
 
D

Default User

Flash said:
On Thu, 19 Aug 2004 08:19:51 -0700


You failed to include stdlib.h which is required for malloc. Your
compiler was required to generate a diagnostic for this line if invoked
in ANSI/ISO mode.


Kind of depends on the compiler. C99 would require this, yes, but not
C89.



Brian Rodenborn
 
K

Keith Thompson

I think it's not so much "compiler magic" but a question how the
underlying hardware works.
[snip]

After free(ptr), the value of ptr becomes indeterminate. That doesn't
require any "compiler magic", since it can become indeterminate
without any change in the bits.

A change in the bits that constitute the representatin of ptr would
require "compiler magic" (and would be of questionable legality).
 
F

Flash Gordon

Kind of depends on the compiler. C99 would require this, yes, but not
C89.

No cast so as I understand it the compiler is required to raise a
diagnostic for assigning an integer (which it assumes malloc returns in
the absence of of a definition) to a pointer. One reason people around
here recommend against casting the return value of malloc :)
 
K

Keith Thompson

RCollins said:
No, this is too deep. Given the standard's definition of free(),
then I should be able to do this:

#include <stdio.h>

int mail(void) {
unsigned long before, after;
int *ptr;

ptr = malloc(100); /* ignore possible errors for now */
before = (unsigned long) ptr;
free(ptr);
after = (unsigned long) ptr;

if (before == after) printf("bits in ptr have not changed\n")
else printf("bits in ptr HAVE changed\n");

return 0;
}

ITYM "main", not "mail" (though of course a function called "mail" is
perfectly valid). And you're probably assuming that an int* will fit
into an unsigned long, which isn't guaranteed.

After the call to free(), the value if ptr is indeterminate. Any
reference to that value, even to convert it to unsigned long, causes
undefined behavior. On most or all real-world systems, the converson
just copies the bits, but it could, for example, load the pointer
value into an address register and trap if the pointer value is
invalid.

Here's my version of your program. Since refers to the representation
of ptr, not to its value as a pointer, it doesn't invoke undefined
behavior.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int *ptr;
unsigned char *before;
unsigned char *after;

/*
* We assume for now that all malloc() calls are successful.
*/
ptr = malloc(sizeof *ptr);
before = malloc(sizeof ptr);
memcpy(before, &ptr, sizeof ptr);
free(ptr);
after = malloc(sizeof ptr);
memcpy(after, &ptr, sizeof ptr);

if (memcmp(before, after, sizeof ptr) == 0) {
printf("bits in ptr have not changed\n");
}
else {
printf("bits in ptr have changed\n");
}
free(before);
free(after);

return 0;
}

This program (assuming all the malloc()s succeed) can print "bits in
ptr have changed" only if there's some compiler magic asociated with
the free() call that fiddles with ptr, even though the free() function
has no way within the language to know where ptr is, or even that the
argument was an object reference.

In my opinion, a conforming compiler cannot perform such magic. It's
not covered by the "as-if" rule because a program can detect it. A
*reasonable* program cannot detect it, and I wouldn't complain if the
standard were amended to allow this particular magic, but I don't
think that's going to happen.
Keep in mind the OP's original question: is there any way to know
if the bit representation in "ptr" is a valid memory location?

And the answer is: no, there's no portable way to do that. There may
be non-portable ways to do it, but only by depending on detailed
knowledge of the implementation (which could easily change with the
next release of the system).
 
K

Keith Thompson

Flash Gordon said:
The code you wanted was something like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
int *ptr;
unsigned char before[sizeof ptr];

ptr = malloc(100); /* ignore possible errors for now */
memcpy( before, ptr, sizeof ptr );
free(ptr);

if (memcmp( before, ptr, sizeof ptr) == 0)
printf("bits in ptr have not changed\n")
else
printf("bits in ptr HAVE changed\n");
return 0;
}

Something like but not exactly like. You're missing a semicolon on
the first printf, and in the memcpy() and memcmp() calls you refer to
ptr where you should refer to &ptr. (As written, you're comparing the
first few bytes of the memory allocated by malloc(), not the bytes
making up the representation of ptr itself.)

Here's another version that fixes the bugs and is an improvement over
the version I posted a few minutes ago:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
int *ptr;
unsigned char before[sizeof ptr];

ptr = malloc(sizeof *ptr); /* assume malloc() succeeds */
memcpy( before, &ptr, sizeof ptr );
free(ptr);

if (memcmp( before, &ptr, sizeof ptr) == 0) {
printf("bits in ptr have not changed\n");
}
else {
printf("bits in ptr HAVE changed\n");
}
return 0;
}

(I had used malloc() to allocate the "before" and "after" arrays
because I forgot that sizeof ptr is a constant expression, a temporary
brain malfunction indirectly associated with the fact that you can't
use sizeof in a preprocessor expression.)
 
R

RCollins

Flash said:
You failed to include stdlib.h which is required for malloc. Your
compiler was required to generate a diagnostic for this line if invoked
in ANSI/ISO mode.

Let's not nit-pic the trivialities; this is example code, not
a lesson for a newbie.
On some REAL implementations thus would cause your program to fail.




Implementation defined behaviour. A pointer might require more bits than
a long.

Section 3.3.4 of the C89 standard expressly allows conversions between
pointers and integer types; it is up to the programmer to insure that
the integer type is wide enough to hold a pointer. Again, this is a
nit-pic; this is an example, not a lesson.
BANG. The pointer might be loaded in to an address register causing the
program to crash. The C standard does not allow you to read the value of
the pointer after you have freed it.

Er ... no. Why would the contents of "ptr" be placed into an address
register? I would expect the address of "ptr" (i.e., &ptr) to be
loaded into an address register, and the contents of "ptr" to be
loaded into a data register.

Also, you can read the contents of "ptr" after a call to free(), but
you cannot read the contents of the memory *pointed to* by "ptr".
if (before == after) printf("bits in ptr have not changed\n")
else printf("bits in ptr HAVE changed\n");

return 0;
}

And I should see that the bit representation in "ptr" have not
changed. Regardless of whether they point to a valid memory
location or not.


The code you wanted was something like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
int *ptr;
unsigned char before[sizeof ptr];

ptr = malloc(100); /* ignore possible errors for now */
memcpy( before, ptr, sizeof ptr );
free(ptr);

if (memcmp( before, ptr, sizeof ptr) == 0)
printf("bits in ptr have not changed\n")
else
printf("bits in ptr HAVE changed\n");
return 0;
}

I will concede the 'properness' of your code; let's just try
to focus on the question at hand.
Copying the bits is allowed, but casting a pointer to an unsigned long
does not copy the bits it takes the value and applies an implementation
defined translation. If you want to copy bits use memcpy as I did and
copy them in to a large enough array of unsigned char as unsigned char
is guaranteed by the standard to not have any trap values.

The bit about "implementation defined translation" is a good point,
and one well worth remembering. However, I can't think of any
modern common compiler that makes such a translation. (It doesn't
mean they don't exist, it just means I can't think of one).
While no machine is required to work that way it's possible to
write a conforming C compiler such a kind of machine. But
therefore you need some allowance in the standard that says
that "the result taking the value of a free()ed pointer is
indeterminate". Also a lot of other seemingly hard to explain
requirements (like, for example, why, when you have an array
a[10], calculating a+20 is undefined) suddenly make a lot of
sense on such a machine.
Regards, Jens

Keep in mind the OP's original question: is there any way to know
if the bit representation in "ptr" is a valid memory location?


There is no standard way to find out. The only value that you cannot
dereference that you can portably detect is the null pointer.

Agreed.
 
R

RCollins

Keith said:
ITYM "main", not "mail" (though of course a function called "mail" is
perfectly valid). And you're probably assuming that an int* will fit
into an unsigned long, which isn't guaranteed.

After the call to free(), the value if ptr is indeterminate. Any
reference to that value, even to convert it to unsigned long, causes
undefined behavior. On most or all real-world systems, the converson
just copies the bits, but it could, for example, load the pointer
value into an address register and trap if the pointer value is
invalid.

Here's my version of your program. Since refers to the representation
of ptr, not to its value as a pointer, it doesn't invoke undefined
behavior.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int *ptr;
unsigned char *before;
unsigned char *after;

/*
* We assume for now that all malloc() calls are successful.
*/
ptr = malloc(sizeof *ptr);
before = malloc(sizeof ptr);
memcpy(before, &ptr, sizeof ptr);
free(ptr);
after = malloc(sizeof ptr);
memcpy(after, &ptr, sizeof ptr);

if (memcmp(before, after, sizeof ptr) == 0) {
printf("bits in ptr have not changed\n");
}
else {
printf("bits in ptr have changed\n");
}
free(before);
free(after);

return 0;
}

This program (assuming all the malloc()s succeed) can print "bits in
ptr have changed" only if there's some compiler magic asociated with
the free() call that fiddles with ptr, even though the free() function
has no way within the language to know where ptr is, or even that the
argument was an object reference.

In my opinion, a conforming compiler cannot perform such magic. It's
not covered by the "as-if" rule because a program can detect it. A
*reasonable* program cannot detect it, and I wouldn't complain if the
standard were amended to allow this particular magic, but I don't
think that's going to happen.

Agreed ... this was the point of my (flawed) example.
And the answer is: no, there's no portable way to do that. There may
be non-portable ways to do it, but only by depending on detailed
knowledge of the implementation (which could easily change with the
next release of the system).

Agreed here, also. I would tend to believe that the desire to
determine if a pointer is "valid" or not is a driving force behind
handle allocation in some OS's (i.e., doubly-indirect memory allocation,
similar to a "pointer to a pointer to an int").
 
D

Default User

Flash Gordon wrote:
No cast so as I understand it the compiler is required to raise a
diagnostic for assigning an integer (which it assumes malloc returns in
the absence of of a definition) to a pointer. One reason people around
here recommend against casting the return value of malloc :)


Yes, of course. I thought you talking about diagnosing the missing
include. The assignment without cast should generate a diagnostic.




Brian Rodenborn
 
K

Keith Thompson

RCollins said:
Flash Gordon wrote: [...]
You failed to include stdlib.h which is required for malloc. Your
compiler was required to generate a diagnostic for this line if invoked
in ANSI/ISO mode.

Let's not nit-pic the trivialities; this is example code, not
a lesson for a newbie.

If you don't want trivialities nit-picked, I humbly submit that you've
come to the wrong newsgroup. :cool:}

[...]
Er ... no. Why would the contents of "ptr" be placed into an address
register? I would expect the address of "ptr" (i.e., &ptr) to be
loaded into an address register, and the contents of "ptr" to be
loaded into a data register.

Why *wouldn't* the contents of "ptr" be placed into an address
register?

If you're going to assume characteristics that are common to most
real-world implementations but aren't guaranteed by the standard, you
might as well just save the value as a pointer:

int *ptr = malloc(whatever);
int *before = ptr;
free(ptr);
if (ptr == before) { /* warning: undefined behavior */
printf("bits didn't change\n");
}
else {
printf("bits changed\n");
}

Or, for that matter:

printf("bits didn't change\n"); /* :cool:} */

If you're going to avoid undefined behavior, there's not much point in
going halfway. memcpy()ing the value to an array of unsigned char
avoids UB; casting to unsigned long doesn't.
Also, you can read the contents of "ptr" after a call to free(), but
you cannot read the contents of the memory *pointed to* by "ptr".

That's incorrect.

In most real-world implementations, you can successfully read both the
contents of ptr and the memory pointed to by ptr after a call to
free(). It's undefined behavior, but most implementations don't do
anything to make it fail. An address is an address, memory is memory,
and you can still read it even if you no longer "own" it. (In some
cases, if the memory is actually returned to the OS and removed from
the program's visible memory space, a reference to the deallocated
memory might actually trap; a reference to just the pointer value is
less likely to do so.)

But as far as the standard is concerned, attempting to refer to the
allocated memory after the call to free() invokes undefined behavior
(as you know) *and* the pointer value itself becomes indeterminate
(even if the bits don't change), so any attempt to refer to that value
will also invoke undefined behavior. Even this:

free(ptr);
if (ptr == NULL) { ... }

or this:

free(ptr);
unsigned long foo = (unsigned long)ptr;

invokes undefined behavior. It happens that the typical result of
this undefined behavior is to read the value of ptr, but the standard
allows nasal daemons.

Why is it defined this way? Because, for example, a conforming
implementation might implement any reference to a pointer value
(including a comparison to NULL or a conversion to unsigned long) by
loading the pointer value into an address register, and the act of
loading the value might do a validity check, and trap if it's invalid.
Such a trap is not required, of course, but the point is that the
standard doesn't render such an implementation non-conforming.

There could be other reasons why referring to the value of ptr might
cause problems; the address register thing is just one possible
example.
 
F

Flash Gordon

Something like but not exactly like. You're missing a semicolon on
the first printf, and in the memcpy() and memcmp() calls you refer to
ptr where you should refer to &ptr. (As written, you're comparing the
first few bytes of the memory allocated by malloc(), not the bytes
making up the representation of ptr itself.)

Oops. That'll teach me to not take a 5 minute break from work to read
and post here. :-/
 
F

Flash Gordon

Let's not nit-pic the trivialities; this is example code, not
a lesson for a newbie.

It is a serious flaw (not that I always post correct code) which will
cause it to fail on some implementations.
Section 3.3.4 of the C89 standard expressly allows conversions between
pointers and integer types; it is up to the programmer to insure that
the integer type is wide enough to hold a pointer. Again, this is a
nit-pic; this is an example, not a lesson.

You've not quoted the exact wording and I don't have the C89 standard.
However, I do not believe (based on posts of long time members of this
group) that it required that there is ANY integer type large enough.
Also, I have seen mention here of implementations where pointers are
definitely larger than the largest integer type.
Er ... no. Why would the contents of "ptr" be placed into an address
register?

Because it is a pointer?
I would expect the address of "ptr" (i.e., &ptr) to be
loaded into an address register, and the contents of "ptr" to be
loaded into a data register.

Also, you can read the contents of "ptr" after a call to free(), but
you cannot read the contents of the memory *pointed to* by "ptr".

No, the value is indeterminate and hence reading it invokes undefined
behaviour. You can still read the representation using a pointer to
unsigned char or by using memcpy etc.
if (before == after) printf("bits in ptr have not changed\n")
else printf("bits in ptr HAVE changed\n");

return 0;
}

And I should see that the bit representation in "ptr" have not
changed. Regardless of whether they point to a valid memory
location or not.


The code you wanted was something like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
int *ptr;
unsigned char before[sizeof ptr];

ptr = malloc(100); /* ignore possible errors for now */
memcpy( before, ptr, sizeof ptr );
free(ptr);

if (memcmp( before, ptr, sizeof ptr) == 0)
printf("bits in ptr have not changed\n")
else
printf("bits in ptr HAVE changed\n");
return 0;
}

I will concede the 'properness' of your code; let's just try
to focus on the question at hand.

Someone else pointed out that my code had flaws as well. :-/
The bit about "implementation defined translation" is a good point,
and one well worth remembering. However, I can't think of any
modern common compiler that makes such a translation. (It doesn't
mean they don't exist, it just means I can't think of one).

I'm sure someone here can point out an implementation with pointers
larger than the largest integer, whether it is modern is another
question.


At least we agree on some things. :)
 
R

RCollins

Keith said:
RCollins said:
Flash Gordon wrote:
[...]
You failed to include stdlib.h which is required for malloc. Your
compiler was required to generate a diagnostic for this line if invoked
in ANSI/ISO mode.

Let's not nit-pic the trivialities; this is example code, not
a lesson for a newbie.


If you don't want trivialities nit-picked, I humbly submit that you've
come to the wrong newsgroup. :cool:}

I think you are right ...
[...]
Er ... no. Why would the contents of "ptr" be placed into an address
register? I would expect the address of "ptr" (i.e., &ptr) to be
loaded into an address register, and the contents of "ptr" to be
loaded into a data register.


Why *wouldn't* the contents of "ptr" be placed into an address
register?

If you're going to assume characteristics that are common to most
real-world implementations but aren't guaranteed by the standard, you
might as well just save the value as a pointer:

int *ptr = malloc(whatever);
int *before = ptr;
free(ptr);
if (ptr == before) { /* warning: undefined behavior */
printf("bits didn't change\n");
}
else {
printf("bits changed\n");
}

Or, for that matter:

printf("bits didn't change\n"); /* :cool:} */

If you're going to avoid undefined behavior, there's not much point in
going halfway. memcpy()ing the value to an array of unsigned char
avoids UB; casting to unsigned long doesn't.

Also, you can read the contents of "ptr" after a call to free(), but
you cannot read the contents of the memory *pointed to* by "ptr".


That's incorrect.

In most real-world implementations, you can successfully read both the
contents of ptr and the memory pointed to by ptr after a call to
free(). It's undefined behavior, but most implementations don't do
anything to make it fail. An address is an address, memory is memory,
and you can still read it even if you no longer "own" it. (In some
cases, if the memory is actually returned to the OS and removed from
the program's visible memory space, a reference to the deallocated
memory might actually trap; a reference to just the pointer value is
less likely to do so.)

But as far as the standard is concerned, attempting to refer to the
allocated memory after the call to free() invokes undefined behavior
(as you know) *and* the pointer value itself becomes indeterminate
(even if the bits don't change), so any attempt to refer to that value
will also invoke undefined behavior. Even this:

free(ptr);
if (ptr == NULL) { ... }

or this:

free(ptr);
unsigned long foo = (unsigned long)ptr;

invokes undefined behavior. It happens that the typical result of
this undefined behavior is to read the value of ptr, but the standard
allows nasal daemons.

Why is it defined this way? Because, for example, a conforming
implementation might implement any reference to a pointer value
(including a comparison to NULL or a conversion to unsigned long) by
loading the pointer value into an address register, and the act of
loading the value might do a validity check, and trap if it's invalid.
Such a trap is not required, of course, but the point is that the
standard doesn't render such an implementation non-conforming.

There could be other reasons why referring to the value of ptr might
cause problems; the address register thing is just one possible
example.

Thanks for the explanation; that was very clear and well presented.
And yet a nagging question keeps going around in my mind ... you
say that a construct of the form

unsigned long foo = (unsigned long)ptr;

can lead to UB (since the compiler may perform some interpretation
of ptr), and yet

unsigned char foo[4]; /* just making an assumption for this example */
memcpy(foo, &ptr, sizeof(ptr));

does _not_ invoke UB. Why not? Isn't the compiler here able
to perform the same interpretation of "ptr" as it did on the
cast to an unsigned long?
 
R

RCollins

Flash said:
On Thu, 19 Aug 2004 14:02:44 -0700



You've not quoted the exact wording and I don't have the C89 standard.
However, I do not believe (based on posts of long time members of this
group) that it required that there is ANY integer type large enough.
Also, I have seen mention here of implementations where pointers are
definitely larger than the largest integer type.

The text is (from C89):
....
Conversions that involve pointers (other than as permitted by the
constraints of $3.3.16.1) shall be specified by means of an explicit
cast; they have implementation-defined aspects: A pointer may be
converted to an integral type. The size of integer required and the
result are implementation-defined. If the space provided is not long
enough, the behavior is undefined.
....

As I noted, it is up to the programmer to make sure the integer type
is wide enough to contain a pointer (and vice versa). And you are
correct, it DOES NOT guarantee that any integer type is wide enough
for the conversion.
Because it is a pointer?

That didn't make sense to me until Keith made his post ... as I
understand it now, an implementation *might* *always* load pointer
values into an address register (or an equivalent construct), and
test for validity.
No, the value is indeterminate and hence reading it invokes undefined
behaviour. You can still read the representation using a pointer to
unsigned char or by using memcpy etc.

See my response to Keith, where I ask "what is the difference between
a cast from ptr vs. a memcpy() from ptr". It seems to me that if
an implementation is *always* going to check the validity of a pointer,
then it would catch it (invalid pointer) regardless of the method used.

At least we agree on some things. :)

We'll agree on more as I learn more.
 
K

Keith Thompson

RCollins said:
Thanks for the explanation; that was very clear and well presented.
Thanks.

And yet a nagging question keeps going around in my mind ... you
say that a construct of the form

unsigned long foo = (unsigned long)ptr;

can lead to UB (since the compiler may perform some interpretation
of ptr), and yet

unsigned char foo[4]; /* just making an assumption for this example */

or unsigned char foo[sizeof ptr]; /* no assumption necessary */
memcpy(foo, &ptr, sizeof(ptr));

does _not_ invoke UB. Why not? Isn't the compiler here able
to perform the same interpretation of "ptr" as it did on the
cast to an unsigned long?

The UB happens when the value of ptr is evaluated *as a pointer*.
Taking its address with "&ptr" is ok because it just gives you the
address of an object you own, without reference to its value.
Computing its size with "sizeof ptr" doesn't evaluate anything; it
just tells you how big the object is (something that can be done at
compilation time). This information is passed to memcpy(), which just
deals with bytes, not with pointers.

And now for something completely different: a Monty Python explanation
of invalid pointers.

The infamous Killer Joke was a joke so funny that anyone hearing it
would immediately die of laughter. The Allies worked to translate it
into German so they could use it as a weapon. Each translator worked
on only one word of the joke (one translator who accidentally saw two
consecutive words spent several weeks in hospital). Each individual
word of the Killer Joke is harmless. Only the entire joke is deadly.

An invalid pointer is the Killer Joke, deadly to anyone who reads it.
The individual bytes composing the pointer are the words of the joke,
harmless in themselves.

The gentlemen of Monty Python are, of course, masters of Undefined
Behavior.
 

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

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top