is NULL-checking redundant in accessor-functions?

D

Douglas A. Gwyn

Jens said:
What you say is correct, but in context wrong. So ...

Actually it was entirely correct.
And again, it is *p that becomes indeterminate, not p.

The value of p itself became indeterminate after it was
passed as an argument to free(). Read the standard.
 
C

Chris Dollin

Jens said:
What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere 'add'.
And again, it is *p that becomes indeterminate, not p.

p becomes indeterminate. That's what makes *p undefined.
The value of p was returned by malloc() and is valid for the implemntation
of p.

The call of free(p) makes the value in p indeterminate. It says "this
pointer value no longer exists".
 
R

Richard Bos

Jens M Andreasen said:
What you say is correct, but in context wrong.

No, that's entirely right in context. There's nothing in the Standard
that forbids that if statement to be compiled to something like

LD ADDR1, null
LD ADDR2, (p)
CMPADDR
JMPEQ afterif

Nor is there anything forbidding that an invalid pointer causes a
segfault at the processor level if it's loaded into an address register.

If p is valid but not null, this results in

LD ADDR1, null (ok, ADDR1 contains a null pointer)
LD ADDR2, (p) (ok, ADDR2 contains the contents of pointer p)
CMPADDR (ok, equal flag is set to zero)
JMPEQ afterif (ok, jump does not take place)

If p is a null pointer, it results in

LD ADDR1, null (ok, ADDR1 contains a null pointer)
LD ADDR2, (p) (ok, ADDR2 contains the contents of pointer p,
which is also a null pointer)
CMPADDR (ok, equal flag is set to one)
JMPEQ afterif (ok, jump does take place)

If, OTOH, the segment that p pointed to has just been deallocated, it
could result in

LD ADDR1, null (ok, ADDR1 contains a null pointer)
LD ADDR2, (p) (not ok. The processor detects an attempt to load a
pointer into ADDR2 which is not valid, and causes a trap. The OS
reacts to this by terminating your program with extreme prejudice.)

Richard
 
S

Stephen Sprunk

Jens M Andreasen said:
Ehrmm, excactly what undefined behaviour?

There is no way of knowing. Your particular implementation _might_ do
something in particular, but there's no requirement for it to nor what it
should be.
It is not my intention to be rude, but I just have not the imagination to
figure out what you mean? Chapter and verse please?

That's the biggest problem programmers seem to have with C; undefined
behavior is exactly that -- undefined.

An implementation is free to do whatever it wants with UB, and it's not
required to document it nor be consistent. It might silently fail one time,
and the next time it might call a hitman on you -- both are perfectly legal
since the behavior is _undefined_.

The problem, for someone trying to write portable code without a copy of the
standard, is that most common implementations do the same things in most
undefined cases and thus the programmer begins to think that behavior is
actually guaranteed, e.g. dereferencing NULL "always" causes a segfault.
Those programmers are quite rudely awakened when porting code to a system
that does something different, e.g. yield interrupt vectors or a NUL
character.

S
 
S

Stephen Sprunk

Jens M Andreasen said:
What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere 'add'.
And again, it is *p that becomes indeterminate, not p.

See 7.20.3.

p is indeterminate after free(p), not just *p. Any operation on p, other
than storing a new value in it, is undefined. IIRC, AS/400 systems in
particular will segfault (or whatever they call it) on "if (p)" or "q=p",
and that's perfectly legal -- it's just not common.
The value of p was returned by malloc() and is valid for the implemntation
of p. Not because malloc has any insight of p, but just because the
definition of malloc!

p is only valid until you call free().

S
 
F

Flash Gordon

Jens said:
We assume so, otherwise p would be NULL, no?
Yes.


Ehrmm, excactly what undefined behaviour?

It is not my intention to be rude, but I just have not the imagination to
figure out what you mean? Chapter and verse please?

From n869:
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

7.20.3 Memory management functions
1 The order and contiguity of storage allocated by successive calls
to the calloc, malloc, and realloc functions is unspecified. The
pointer returned if the allocation succeeds is suitably aligned so
that it may be assigned to a pointer to any type of object and
then used to access such an object or an array of such objects in
the space allocated (until the space is explicitly freed or
reallocated). Each such allocation shall yield a pointer to an
object disjoint from any other object. The pointer returned points
to the start (lowest byte address) of the allocated space. If the
space cannot be allocated, a null pointer is returned. If the size
of the space requested is zero, the behavior is
mplementation-defined: either a null pointer is returned, or the
behavior is as if the size were some nonzero value, except that
the returned pointer shall not be used to access an object. The
value of a pointer that refers to freed space is indeterminate.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I believe the actual C90 and C99 standards have similar wording.
 
F

Flash Gordon

Jens said:
What you say is correct, but in context wrong. So ...

Mmmm ... no! You would need a 'move' to get there and not a mere 'add'.
And again, it is *p that becomes indeterminate, not p.

The value of p was returned by malloc() and is valid for the implemntation
of p. Not because malloc has any insight of p, but just because the
definition of malloc!

If you really want p to be out of range, you will have to
call rand() or some such.

No, the value of p becomes indeterminate when free is called on p, thus
evaluating p invokes undefined behaviour. See my other post for the C&V
 
F

Francis Glassborow

Chris Torek said:
*This* is how p can become invalid after free(): not by having its
stored bit pattern change, but rather by having the *meaning* of
those bits change.

And there is another context in which the bits can actually change.

Consider:

int * array[1000] = {0};
/* code initialising the pointers
* to dynamically assigned storage */
/* code using the values */
for(i = 0; i < 1000; i++) free(array);

at this point the 'values' in the array are indeterminate and the
compiler 'knows' there is no reason they need to be preserved. Therefore
it does not need to page out those values should another process/thread
wish to use the physical storage, nor page them back in when this
process resumes. No conforming program can detect the difference.

There is a DR that queries whether inspection of indeterminate values
by using an aliasing array of unsigned char requires stability of bit
patterns for indeterminate values. The current proposed committee
response is that it does not. Any and all possible bit patterns are
equivalent for an indeterminate value.
 
J

Jens M Andreasen

You are wrong and Doug Gwyn is right, at least formally speaking.

OK, I'll buy 'formally speaking' ...
(Implementations that actually catch the "if (p)" error after the free(p)
call are somewhere between rare and nonexistent.)

Perhaps even touching nonexistent?
This issue is quite tricky, but it may become clear if you can answer me
this question: what is the value represented by the following bit pattern?

11000011 11110101 01001000 01000000
(in hex: 0xc3 0xf5 0x48 0x40)

I will give you two possible answers (though there are more):

3.14 (a float)
1078523331 (an int)

and will I tell you that at least one of them is correct (and the machine
in question has a Pentium III CPU).

Depends on what register your bitpattern resides in. Given the option
between float and int, my guess is one of the SSE registers?

The answer depends on what instruction you are to issue next, no?

The latencies though, between SSE and adress registers prohibits practical
use of SSE to calculate offsets.

(and yes, I am aware of the wonderful weirdness of Intel pointer
bitpatterns, but even in asm they are hard to actually get at)
How will you decide whether those bytes represent a float or an int?
More importantly, whether I tell you the truth or lie -- it does not
really matter which -- how can it be that this same bit pattern
apparently represents two different numbers?

The answer lies in the *interpretation* of the bits. The bits themseves
are just bits. By themselves they have no deeper meaning. It is only
when I tell you "these are the bits of a float" that they *mean* 3.14,
or "these are the bits of an int" that they *mean* 1078523331.

OK ...
What if I tell you, instead, that the machine is a Deathstation 9900,
and the bits represent a pointer? What is the value of that pointer?
How will you interpret them?

I wouldn't ...

You are now saying that:

free(p)
if(p)
...

.... *could* result in a cast to float whith a possible raise of an
exception (NaN or whatever)

That means free() would deliberately have to set the register where it
found p to an evil pattern. And if p was on the stack (or some such),
nobody would even ever notice?

This is longwinded.
Originally, yes.


Valid until free()d, yes.

Formally ...
But what if free() changed something in the hardware so that the very
same -- unchanged -- bits in p, that *used* to mean "the contents of the
memory bank located in the south wing of the building", now mean "please
trigger the nuclear warhead"? (This is, after all, the Deathstation.
:) )

OK, on the Deathstation perhaps. I think you have now touched the
unimplemented part of the world, no?
;-)
There is nothing in the C language that prohibits free() from changing
the way the machine interprets the bits. If certain pointer bit
patterns were like "int" before (always valid), and are like "float" now
(have trap representations), the simple act of testing the value of "p"
can fault.

I would buy this if we have had a construct like:

p = free(p);

.... but this is not the case.

*This* is how p can become invalid after free(): not by having its
stored bit pattern change, but rather by having the *meaning* of those
bits change.

Ehrmm, p is still declared as a pointer and not a float. I assume my
compiler to still evaluate it as a pointer. How then can the meaning of
the (unchanged) bitpattern have changed (in an implementation other than
the imaginary evil Deathstation?)


mvh // Jens M Andreasen
 
J

Jens M Andreasen

p is indeterminate after free(p), not just *p. Any operation on p, other
than storing a new value in it, is undefined. IIRC, AS/400 systems in
particular will segfault (or whatever they call it) on "if (p)" or "q=p",
and that's perfectly legal -- it's just not common.


OK, now we are getting somewhere.

Can anybody with current access to an AS/400 confirm that if(p) indeed
fails after free?

mvh // Jens M Andreasen
 
J

Jens M Andreasen

If, OTOH, the segment that p pointed to has just been deallocated, it
could result in

LD ADDR1, null (ok, ADDR1 contains a null pointer) LD ADDR2, (p) (not
ok. The processor detects an attempt to load a
pointer into ADDR2 which is not valid, and causes a trap. The OS
reacts to this by terminating your program with extreme prejudice.)

This is perhaps the best description of the situation so far

I would have thought that it is the following MOVE that raises the
exception. Afterall we haven't done anything nasty (yet.)

But the implementation, as described, makes sense in context of
implementing an efficient MMU.

Realworld examples, please?


mvh // Jens M Andreasen
 
D

Dave Hansen

On Fri, 21 Jan 2005 04:02:41 -0000, "Stephen Sprunk"

[...]
That's the biggest problem programmers seem to have with C; undefined
behavior is exactly that -- undefined.

An implementation is free to do whatever it wants with UB, and it's not
required to document it nor be consistent. It might silently fail one time,
and the next time it might call a hitman on you -- both are perfectly legal
since the behavior is _undefined_.

Even worse, it might do exactly what you expect. Every time. Leading
one to the (incorrect) conclusion that the behavior in that case isn't
undefined. "It works on _my_ system, so it _must_ be OK."

Regards,

-=Dave
 
C

Christian Bau

Chris Torek said:
There is nothing in the C language that prohibits free() from
changing the way the machine interprets the bits. If certain
pointer bit patterns were like "int" before (always valid), and
are like "float" now (have trap representations), the simple act
of testing the value of "p" can fault.

*This* is how p can become invalid after free(): not by having its
stored bit pattern change, but rather by having the *meaning* of
those bits change.

There is another possibility: An optimising compiler can be quite
clever. For example, an optimising compiler can know that the result of
malloc (), if it is not a null pointer, cannot equal any other pointer.
Or a compiler can know that a variable containing a pointer that is
free'd has an indeterminate value after the call to free (), and
therefore cannot be equal to any other pointer.

Example:

void test (void *p)
{
if (p != NULL) {
void* q = p;
free (p);
if (p != q) printf ("Gotcha!!!\n");
}
}

The compiler can _know_ that the value of p is indeterminate after the
call free (p), and therefore p != q must be true. (A compiler can also
_know_ that p != q must be false, or it can compare p and q and anything
can happen. That is undefined behavior for you).
 
C

Christian Bau

"Stephen Sprunk said:
That's the biggest problem programmers seem to have with C; undefined
behavior is exactly that -- undefined.

An implementation is free to do whatever it wants with UB, and it's not
required to document it nor be consistent. It might silently fail one time,
and the next time it might call a hitman on you -- both are perfectly legal
since the behavior is _undefined_.

And if you don't believe that: Most so-called computer viruses take
advantage of undefined behavior in C programs; a relatively common
consequence of undefined behavior is that your computer uses its modem
to dial a phone sex line which runs up a phone bill of a few hundred
pound in short time.
 
K

Keith Thompson

Jens M Andreasen said:
Yes I read the article, but it appeared to be on if(*p) which I believe is
different from if(p)

You're right, my mistake. In the referenced article I was explaining
that the undefined behavior caused by an attempt to dereference a null
pointer doesn't necessarily cause a trap and terminate the program.
I shall continue my crusade against 'p == NULL considered dangerous!'

We're not necessarily saying it's dangerous; we're saying the standard
doesn't guarantee that it isn't dangerous.

To re-state the context:

int *p;
p = malloc(sizeof *p); /* assume malloc() succeeds */
free(p);
if (p == NULL) /* undefined behavior here */
{ ... }

I think this has been explained at length in this thread.

Again, undefined behavior is behavior for which the standard imposes
no requirements. Even if every actual implementation does the same
thing, or even if every *possible* implementation does the same thing,
the behavior is still undefined as far as the standard is concerned.

The comparison (p == NULL) might always evaluate to false; the point
is that the standard doesn't guarantee it. (And in any case, since
the standard doesn't guarantee the behavior, an optimizing compiler
can "cheat" in any way it likes.)
If nothing else, it has entertaining value ...

Um, not really.
 
D

Douglas A. Gwyn

Jens said:
Ehrmm, p is still declared as a pointer and not a float. I assume my
compiler to still evaluate it as a pointer. How then can the meaning of
the (unchanged) bitpattern have changed (in an implementation other than
the imaginary evil Deathstation?)

The object is still of pointer type, but it no longer
has the same "value" (meaning) despite the bit pattern
being unchanged. How this can actually happen has been
explained in this thread, more than once.
 
D

Douglas A. Gwyn

Jens said:
I would have thought that it is the following MOVE that raises the
exception. Afterall we haven't done anything nasty (yet.)

Yes you have: you tried to load an unmapped value
into an address register. A trap is highly appropriate.
 
G

Gordon Burditt

Ehrmm, p is still declared as a pointer and not a float. I assume my
compiler to still evaluate it as a pointer. How then can the meaning of
the (unchanged) bitpattern have changed (in an implementation other than
the imaginary evil Deathstation?)

How can the usability of a credit card change when it is reported
stolen? The number on the card is unchanged.

Memory maps can change during the life of a process. Segments can
become valid or invalid (and having this happen during calls to
malloc() or free() is not unreasonable). Loading a "large" pointer
with an invalid segment into an i386 segment register will cause a
trap. No dereference is required (you won't get a chance to try).
A NULL pointer is treated as a special case here: you can load the
pointer, but not dereference.

Gordon L. Burditt
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
474,159
Messages
2,570,879
Members
47,416
Latest member
LionelQ387

Latest Threads

Top