Joe Wright said:
Keith said:
Joe Wright <
[email protected]> writes:
[...]
The zero value is
chosen specifically because it is within the range of all possible
pointer values.
I'm not sure what this means. Pointers are not numbers; they don't
have ranges.
Pointer values share some characteristics of numbers. You can add to
them, subtract from them and subtract one from another. Pointers have
a range from 0 to the maximum allowed memory address.
Yes, they share *some* of the characteristics of numbers, but not all
of them. For example, pointer arithmetic is defined only with a
single object. There is nothing in the standard that supports the
idea that pointers range from 0 to the "maximum allowed memory
address".
As the C programmer doesn't know the memory model of the target, the
natural choice for a 'pointer to nothing' would be 0 or (void*)0.
There's nothing natural about it *unless* you assume a particular
linear memory model. C's definition of null pointer constants is a
relic of the early systems on which it was implemented, which happened
to have such a model (as do many, probably most, systems today).
Think of a pointer as a nearly opaque entity that can designate an
object or function.
A function pointer could be implemented as an index into a table of
functions, having nothing to do with a machine address.
Any valid object pointer value is one of the following:
A null pointer;
A pointer to an object plus an offset (possibly zero) within that
object; or
Something implementation-specific.
(The second is a special case of the third, with a zero offset.) An
implementation could represent each object pointer as a structure
consisting of an object descriptor (perhaps a table index, perhaps
something else) and an offset. Pointer arithmetic would act only on
the offset. No portable code can tell the difference between this and
the more common implementation as a machine address.
And a null pointer is just a special representation (or
representations) indicating that the pointer doesn't point to
anything. There's no fundamental reason why all-bits-zero is a good
choice for this. It's like a floating-point NaN (Not-a-Number);
there's no reason for portable code to care how it's represented.
In fact, I think that demonstrates a flaw in the C model. It's
assumed that there's a "zero" value for each scalar type, and that
these zero values are somehow analagous to each other. But in fact,
there is no integer value corresponding to a null pointer. 0 is just
another ordinary value within the range of valid values; likewise for
a floating-point 0.0. The best analogy is between a nul pointer value
and a floating-point "quiet NaN".
A language slightly better designed than C would have a keyword,
perhaps "nil", to denote a null pointer value; it would be the *only*
valid null pointer constant. Instead, we have some special-case rules
about integer constant expressions with the value 0. These rules are
probably necessary for backward compatibility. Fortunately we can
hide the ugly details behind the NULL macro.
If a program needs to get closer to the metal and use the underlying
machine address representation, an implementation can provide rules
for converting integers to pointers -- but those rules are entirely
implementation-defined (except for the null pointer constant anomaly)
and might not even make sense on a machine with non-linear addressing.
Consider..
int *ptr;
ptr = malloc(100 * sizeof *ptr);
if (ptr == NULL) {/* do something about the failure */}
.. use ptr with careless abandon ..
free(ptr);
.. use ptr at your peril ..
The value of ptr probably hasn't changed but the call to free(ptr) has
made it indeterminate. You can't examine ptr to determine its validity.
Right, but that doesn't support your earlier statement. *Any* pointer
value other than an indetermine one can be tested for validity; you
did so on the second line of your example (if malloc() succeeded).