Just how delicate are freed pointers?

L

Leor Zolman

pete said:
Leor said:
David Scarlett wrote:

Is the following code safe?

No.

Hmmm. Assuming a was valid before the free() and b still is at the
point of the comparison, I don't get why you think this is unsafe.
Personally, it seems strange to test the value of a pointer after it
has been freed (and your version is more conventional in that sense),
but since we're only testing the /pointer/ and not trying to access
the memory it is pointing to, why wouldn't the OP's code be "safe"?
-leor

N869
7.20.3 Memory management functions
[#1]
The value of a pointer
that refers to freed space is indeterminate.

So the correct thing for a compiler to do after call "free (a)" would be
to treat a the same as an uninitialised variable (uninitialised
variables have indeterminate values as well), giving a warning when you
try to use it and so on.

As I understand it, violations of things not in a "Constraints" section of
the Standard are not required to draw a diagnostic; compilers are free to
emit or not emit diagnostics as they please (IOW, it is a compiler QOI
issue).

In any case, this sounds more like something that might fall into the
domain of a lint-like program, if anywhere, rather than the compiler.
Consider code such as the following:

char *p, *q, *r;
p = malloc(...);
q = malloc(...);
r = q;
...
free(q);
...
if (p == r ) /* oops, can't test q...er, r! */
...

Would you expect a compiler to be "smart enough" to know that testing r is
actually testing the freed value of q, and warn about it?
-leor
 
J

Jack Klein

I find this statement highly ambiguous.

How so? Both the terms "indeterminate value" and "undefined behavior"
are specifically defined in the standard itself.

And as for a freed pointer, paragraph 2 of section 6.2.4 "Storage
duration of objects" specifically states:

"The value of a pointer becomes indeterminate when the object it
points to reaches the end of its lifetime."

It then refers to section 7.20.3 "Memory management functions" for the
specifics of dynamically allocated objects. This states:

"The lifetime of an allocated object extends from the allocation until
the deallocation."

This makes it quite clear that once a pointer obtained from
malloc/calloc/realloc is passed to free, the pointer's value becomes
indeterminate.

Or do you doubt the connection between the use of objects of
indeterminate value and undefined behavior?
 
J

Jack Klein

I've heard this stated before, and I completely understand that loading a
/garbage/ address into an address register under one of these architectures
is bad ju-ju. What makes me wonder in the case of the OP's scenario,
however, is my (admittedly, perhaps outdated) recollection of how memory
allocation works in the C libraries. Under the last systems where I took
notice of such things, once memory had been obtained from the system (on
Unix, it was via an sbrk() call, I believe), it remained allocated to the
process. So I was thinking in terms of "once hardware-validated, always
hardware-validated" during the run of that process...even on those
sensitive architectures. AFAIK, this may no longer be true on modern
systems, though...anyone care to set me straight?
-leor

There are far more platforms using virtual memory management on modern
processors than just UNIX variants. The point is that at the time
memory is freed, it might very well be unmapped by the memory
management hardware, thus rendering its address invalid.
 
C

Christian Bau

CBFalconer said:
Splutter. Hogwash. Bullshit. Merde. Schiesse. :) I think
you mean that such a change is one of the many undefined
behaviours available.

Yes. Too many people are caught in their little world of
x86/PowerPC/Mips/Alpha processor and think that these processors define
the behavior of the C language. That is most definitely not so.

In many cases the result of undefined behavior will be that the compiler
never notices the undefined behavior, translates your code in a natural
way, and the undefined behavior will be whatever the processor does. But
that is by far not the only thing that can happen.

An optimising compiler will make the assumption that your code invokes
no undefined behavior. The compiler is absolutely allowed to do this.
The assumption may be wrong, but any damage done as a result is the
fault of the programmer who invoked undefined behavior, not the fault of
the compiler. Then the compiler can draw any logical conclusions from
the assumption that you invoke no undefined behavior and compile
according to these conclusions.

When you look at the "restrict" specification in C99: What it
essentially says is that "restrict" is now a reserved word, you can
insert the keyword "restrict" into legal code in certain places, and
certain code that had defined behavior has undefined behavior when
"restrict" has been added. So what is "restrict" good for? It allows the
compiler to make assumptions about your code and therefore generate
faster code.
 
J

Joona I Palaste

CBFalconer said:
Christian Bau wrote:
... snip ...
Splutter. Hogwash. Bullshit. Merde. Schiesse. :) I think
you mean that such a change is one of the many undefined
behaviours available.

You mean "scheisse". If there is "ei" or "ie" in a German word, you
can depend on native English speakers to spell it wrong. Likewise if
there is "ou" or "uo" in a Finnish word, you can depend on Swedes to
spell it wrong.
 
K

Keith Thompson

Christian Bau said:
N869
7.20.3 Memory management functions
[#1]
The value of a pointer
that refers to freed space is indeterminate.

So the correct thing for a compiler to do after call "free (a)" would be
to treat a the same as an uninitialised variable (uninitialised
variables have indeterminate values as well), giving a warning when you
try to use it and so on.

Quibble:

As far as the standard is concerned, the "correct" thing for a
compiler (for code that references a after calling free(a) is anything
it likes. It's undefined behavior; the standard does not require any
sort of diagnostic.

But yes, a sufficiently clever and friendly compiler might give a
warning. This is a QoI (Quality of Implementation) issue, not a
correctness issue.
 
S

Stephen Sprunk

Stephen L. said:
I did read the thread.

Actually, you should read the _last_ thread on this, just a few weeks ago.
Then you'd know exactly why you're wrong, including citations from the
Standard.

It's okay to be wrong every now and then, but when someone contradicts
you --especially half a dozen someones -- you might want to double-check
your facts (and provide citations) before asserting you're right and
everyone else is wrong.
Sorry, the above is utter nonsense.

Nope, it's valid on some implementations and allowed by the Standard. See
the previous thread for citations.
Pointer arithmetic is always legal
no matter what the state of the _value_
of that pointer. This is a fact.

Nope. Even examining the value of an invalid pointer is undefined behavior.
It may work on some platforms, but the Standard doesn't guarantee it.
It's not clear to me that some posters know
to distinguish the operations of "pointer
arithmetic" and "dereferencing the result"
of such arithmetic...

It's not clear that you understand the meaning of "undefined behavior" as
applied to examining invalid pointers. Doing arithmetic, checking for
equality, assigning them, and many other operations cause UB, not just
dereferencing.

You are confusing how your platform acts with what the Standard says.


S
 
K

Keith Thompson

CBFalconer said:
Splutter. Hogwash. Bullshit. Merde. Schiesse. :) I think
you mean that such a change is one of the many undefined
behaviours available.

Yes, it's one of the many undefined behaviors available, but I think
Christian's statement is correct.

A compiler is allowed to perform transformations based on the
assumption that no undefined behavior occurs. (If no undefined
behavior occurs, the compiler was right, and the transformation is ok;
if undefined behavior does occur, the compile can do anything it
likes.)

So given a function like this:

void func(int *ptr)
{
free(ptr);
if (some_other_ptr == ptr) {
...
}
}

the compiler may replace the comparison "some_other_ptr == ptr" with
"some_other_ptr == NULL". This transformation will not cause any
valid program to misbehave.

(Transforming "some_other_ptr == ptr" to "some_other_ptr == NULL" may
not seem like much of an optimization, but if the compiler knows from
data flow analysis that some_other_ptr is null, or that it isn't, it
can eliminate the comparison altogether).
 
K

Keith Thompson

Keith Thompson said:
Quibble:

As far as the standard is concerned, the "correct" thing for a
compiler (for code that references a after calling free(a) is anything
it likes. It's undefined behavior; the standard does not require any
sort of diagnostic.

Sloppy proofreading strikes again. What I meant was:

As far as the standard is concerned, the "correct" thing for a
compiler to do (for code that references a after calling free(a)) is
anything it likes. It's undefined behavior; the standard does not
require any sort of diagnostic.
 
C

CBFalconer

Stephen said:
.... snip ...

Nope, it's valid on some implementations and allowed by the
Standard. See the previous thread for citations.

Which is an argument for always using:

#define FREE(x) do {free(x); x = NULL;} while (0)

ensuring we can always use x normally in equality comparisons.
 
D

Dan Pop

Because the standard says so.

Because the standard provides no guarantees about the value of a pointer
after the expiration of the pointed-to object. There are two possible
interpretations of the standard in this context:

1. The pointer representation is preserved, but it may become a trap
representation.

2. The implementation is free to even change the pointer representation.

A long debate in comp.std.c reached no conclusion.
N869
7.20.3 Memory management functions
[#1]
The value of a pointer
that refers to freed space is indeterminate.

Replaced in the final standard by:

The lifetime of an allocated object
extends from the allocation until the deallocation.

Which combines with the more general 6.2.4p2

The value of a pointer becomes indeterminate when
the object it points to reaches the end of its lifetime.

to give the same effect: undefined behaviour if the pointer is evaluated.

Dan
 
L

Leor Zolman

Sloppy proofreading strikes again. What I meant was:

As far as the standard is concerned, the "correct" thing for a
compiler to do (for code that references a after calling free(a)) is
anything it likes. It's undefined behavior; the standard does not
require any sort of diagnostic.

That's actually what I saw the first time -- probably the reason so many of
my own posts contain typos like that ;-)
-leor
 
D

Dan Pop

In said:
Leor said:
David Scarlett wrote:

Is the following code safe?

No.

Hmmm. Assuming a was valid before the free() and b still is at the
point of the comparison, I don't get why you think this is unsafe.
Personally, it seems strange to test the value of a pointer after it
has been freed (and your version is more conventional in that sense),
but since we're only testing the /pointer/ and not trying to access
the memory it is pointing to, why wouldn't the OP's code be "safe"?
-leor

N869
7.20.3 Memory management functions
[#1]
The value of a pointer
that refers to freed space is indeterminate.

Ok, thanks! That makes it pretty clear. In the copy of the C99 Standard I
have (ISO/IEC 9899, "Second edition", 1999-12-01) they've moved that to the
last line of 6.2.4/2.

In appendix J.2 ("The behavior is undefined in the following circumstances"
followed by 11 pages of circumstances), I did find this item (page 501):

The value of a pointer that refers to space deallocated by a
call to the free or realloc function is used (7.20.3).

The interesting thing is the reference is back to 7.20.3, where you saw the
statement back in the draft. It looks to me as if they moved the statement
but perhaps forgot to update the note in J.2.

Nope, the reference to 7.20.3 is correct, even if the referenced text was
changed to:

The lifetime of an allocated object
extends from the allocation until the deallocation.

Dan
 
D

Dan Pop

In said:
You mean "scheisse". If there is "ei" or "ie" in a German word, you
can depend on native English speakers to spell it wrong. Likewise if
there is "ou" or "uo" in a Finnish word, you can depend on Swedes to
spell it wrong.

And you SHOULD add an [OT] tag in the subject line when making off topic
comments/corrections!

Dan
 
J

Joona I Palaste

And you SHOULD add an [OT] tag in the subject line when making off topic
comments/corrections!

So should you. =)
Erm, anyway, sorry, I just forgot. It's there now. =)
 
S

Stephen L.

Christian said:
Interesting that you quote a post that explains quite nicely _why_
accessing "a" after "free (a)" is wrong, and then you say it is ok.
Well, it is not. You will mostly get away with it in this case, but it
is wrong. It will go wrong in the following cases, and perhaps in
others:

My quoting of the post was not to show my agreement.
1. Unusual hardware.
2. C interpreter.
3. Brutally optimising C compiler.

I think we're getting a little nutty here.

I dug up an olde copy of the C Standard. The
last sentence of 7.20.3 simply states that

"The value of a pointer that refers to
freed space is indeterminate."

The standard is quite specific about the definition
and use of the term "undefined behavior". In the
whole 7.20.3 paragraph, that phrase is not used
once. If someone has a copy of the standard that
uses that phrase in 7.20.3, I'm very interested
in seeing that version. If the standard writers
meant "undefined", they surely would have said
"undefined".

I contend that if there is a compiler out there
that invokes undefined behavior (I don't care
if it's x86 architecture or not), that compiler
is not standard conforming to 7.20.3 and has a bug.

I'm not convinced that a compiler would spend the
instructions to load up a pointer (register set)
on an x86 architecture if it was not going to
perform indirection through that pointer (register set).
I'm a little rusty on my x86, but can you perform
arithmetic operations (like compare) on the x86
pointer register set? Doesn't the compiler have
to move the values to GP register(s) to perform
arithmetic comparisons?


It's not the value of "a" which was valid before
the call, it's the validity of what "a" pointed to
before/after the call to `free()' that has changed.

This is a subtle but important distinction and
the last sentence of 7.20.3 supports this IMHO.


Remember, the OP's example (some 300 posts ago :) ).
For whatever reason, he was shadowing "a" in "b" ->

David Scarlett wrote:

Is the following code safe?

SomeType *a, *b;

/* Some code.... */

free(a);

if ( a == b ) b = NULL;

a = NULL;


Suppose instead of his calling `free(a)',
he called a function in some library
he authored that called `free(a)'.

How does that change the behavior of the
code snippet now, and would this new
behavior still be consistent with 7.20.3?


Stephen
 
R

Richard Bos

Stephen L. said:
I think we're getting a little nutty here.

I dug up an olde copy of the C Standard. The
last sentence of 7.20.3 simply states that

7.20.3 is a red herring, because the undefined behaviour here is because
of a more general rule:

# 3.18
# [#1] undefined behavior
# behavior, upon use of a nonportable or erroneous program
# construct, of erroneous data, or of indeterminately valued
# objects, for which this International Standard imposes no
# requirements

Using an indeterminately valued object _always_ invokes UB unless the
Standard imposes other requirements. The very fact that 7.20.3 does
_not_ mention indeterminately valued pointers means that asking free()
to use one does invoke UB because of the more general rule in 3.18.

Richard
 
E

Eric Sosman

Leor said:
Hmmm. Assuming a was valid before the free() and b still is at the
point of the comparison, I don't get why you think this is unsafe.

Stop right there: Your second assumption is incorrect.

6.4.4 Storage durations of objects
/2/ [...] The value of a pointer becomes indeterminate
when the object it points to reaches the end of its
lifetime.

You've got two pointers to one dynamically-allocated object.
Pass one of the pointers to free(), and the object ceases to
exist. When the object ceases to exist, the values of both
pointers become indeterminate.
 
E

Eric Sosman

pete said:
Leor said:
Hmmm. Assuming a was valid before the free() and b still is at the
point of the comparison, I don't get why you think this is unsafe.
Personally, it seems strange to test the value of a pointer after it
has been freed (and your version is more conventional in that sense),
but since we're only testing the /pointer/ and not trying to access
the memory it is pointing to, why wouldn't the OP's code be "safe"?
-leor


N869
7.20.3 Memory management functions
[#1]
The value of a pointer
that refers to freed space is indeterminate.

This sentence is absent from the final C99 Standard,
presumably because it makes no sense. (If the pointer's
value is indeterminate, how can that pointer be said to
refer to anything? But if the pointer actually refers to
something, how can its value be called indeterminate?)
 
K

Keith Thompson

CBFalconer said:
Which is an argument for always using:

#define FREE(x) do {free(x); x = NULL;} while (0)

ensuring we can always use x normally in equality comparisons.

I suppose it could be useful for something like this:

while (some_condition) {
if (some_other_condition) {
FREE(x);
break;
}
}
if (x == NULL) { /* check whether x was free'd */
blah;
}

But more commonly, I think, code that checks the value of x after
calling free() is buggy. Setting x to NULL is as likely to mask the
bug as to fix it.
 

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,780
Messages
2,569,611
Members
45,280
Latest member
BGBBrock56

Latest Threads

Top