validity of a pointer

K

Keith Thompson

Stephen Sprunk said:
There exist some systems where zero is a "valid" pointer, e.g. in MS
DOS it's a pointer to the interrupt vector table.

There is no portable way to test for "indeterminate" pointers, and
even if your OS provides a non-portable function to perform a test,
technically calling it can invoke UB because an argument must be
evaluated to be passed, and examining an indeterminate pointer (not
just dereferencing it) causes UB...

If your OS provides such a function, your C implementation probably
also guarantees (or *could* guarantee) that examining an indeterminate
pointer value doesn't cause an unhappy situation.
 
W

Wolfgang Draxinger

Stephen said:
There exist some systems where zero is a "valid" pointer, e.g.
in MS DOS it's a pointer to the interrupt vector table.

I fell for the same misconception until a few months ago (when
Keith Thompson and a few others had the patience, to explain it
to me, thanks again guys). The value of a pointer in C may be
something completely different from what is actually stored in
the machine. If your architecture consideres 0xdeadbeef as a
null pointer, then the C standard states, that

void *p = something;
if( p == 0 ) { ... }

is actually compiled to a comparision of p against 0xdeadbeef.

If you wonder how to access things like the interrupt vector
table then. Well, you need a compiler that has been writen for
that system (e.g. by applying a offset of -0x1000 to all pointer
values found in the range 0x1000...0x2000 if dereferenced and
the inverse if casted to an int). And you declare your pointer
extern (and constant).

extern void * const intvec;

And then in the linker you direct intvec to point to the correct
location.

The funny thing is: C is much more high level then most
implementations make people think. One could perfectly compile C
into interpreted bytecode.

Wolfgang
 
W

Wolfgang Draxinger

jameskuyper said:
same as always considering that any non- null pointer is valid.

I wouldn't consider any pointer valid, but one can't be sure if a
given pointer value is valid or not. 0 is the only exception. If
a pointer is 0 you know it's invalid.

Any saying "any pointer values should be considered valid" does
not mean, you should write something like:

#include <stdint.h>
uintptr_t j;
for(j = 0; j < UINTPTR_MAX ; ++j) {
putc( *((char*)j) );
}

The statement means, that the following is not reasonable,
without knowing details of the implementation (in this case
r_min, r_max).

#include <stdint.h>
extern uintptr_t r_min, r_max;
extern void *p;
if( (uintptr_t)p < r_min || (uintptr_t)p > r_max ) {
printf("pointer p is invalid");
}

You don't know if it's valid or invalid. And on some systems
(like DOS) dereferencing a invalid pointer will not raise a bus
error, if you don't address nonexisting memory. And on such
systems, where programs share the address space, you should
assume a safe strategy, considering every pointer you've no
track of as being valid, and unknowingly touching it may couse
severe trouble in other parts of the system. So saying "consider
every pointer valid" does not mean "do with it what ever you
want" but instead: "treat it carefully, you don't know, what it
might do, if you don't. It could be even the trigger of a
doomsday device..."

Wolfgang
 
C

Chris Dollin

Wolfgang said:
The funny thing is: C is much more high level then most
implementations make people think. One could perfectly compile C
into interpreted bytecode.

That's not a test of high-level-ness, since one could compile
assembly code into interpreted bytecode.
 
F

Francois Cartegnie

Wolfgang said:
only "invalid" pointer in terms of C is the null pointer, with
the value 0.
Just 2 cents:
"with the value defined in headers".
I guess that any well written code (not referring to 0) should work with
null redefined to MAX_INT (as long it's not in it's allocatable address
range)
 
J

James Kuyper

Wolfgang said:
I fell for the same misconception until a few months ago (when
Keith Thompson and a few others had the patience, to explain it
to me, thanks again guys). The value of a pointer in C may be
something completely different from what is actually stored in
the machine. If your architecture consideres 0xdeadbeef as a
null pointer, then the C standard states, that

void *p = something;
if( p == 0 ) { ... }

is actually compiled to a comparision of p against 0xdeadbeef.

If you wonder how to access things like the interrupt vector
table then. Well, you need a compiler that has been writen for
that system (e.g. by applying a offset of -0x1000 to all pointer
values found in the range 0x1000...0x2000 if dereferenced and
the inverse if casted to an int). And you declare your pointer
extern (and constant).

Yes, an implementation is allowed to use such an approach; but that's
not what was done in the case Stephen was describing. A pointer to
address 0 was treated as a null pointer; the undefined behavior that
occurred as a result of dereferencing such a null pointer was that it
produced a reference to start of the piece of memory containing the
interrupt vector table.
 
J

James Kuyper

Francois said:
Just 2 cents:
"with the value defined in headers".
I guess that any well written code (not referring to 0) should work with
null redefined to MAX_INT (as long it's not in it's allocatable address
range)

Well written code can assume that null isn't even defined; it certainly
doesn't need to be able to cope with null pre-defined to the same value
as MAX_INT. The following is perfectly legal code that would become a
syntax error with that definition:

char null[] = "NULL is not the same thing as null.";

If you were actually talking aboue NULL (rather than null), then your
statement is still incorrect. NULL cannot legally be defined to have a
value of MAX_INT, and well-written code therefore need not consider the
possibility that it is. NULL can't legally be defined as (void*)MAX_INT,
either, though it is permissible for (void*)MAX_INT to be null, in which
case it must compare equal to NULL.

I suspect that some of my statements above will sound meaningless,
nonsensical, or contradictory to you. If so, please identify which ones,
and I'll explain further.
 
H

Harald van Dijk

[...] NULL can't legally be defined as (void*)MAX_INT,
either, though it is permissible for (void*)MAX_INT to be null, in which
case it must compare equal to NULL.

If NULL is defined as ((void*)MAX_INT), which requirement of the standard
has the implementation not met if a program cannot tell ((void*)0) apart
from ((void*)MAX_INT)? The one thing I can think of is that (void*)0 can
be implicitly converted to a function pointer type, but implicit
conversions between object and function pointer types are somewhat
commonly supported already anyway.
 
J

James Kuyper

Harald said:
[...] NULL can't legally be defined as (void*)MAX_INT,
either, though it is permissible for (void*)MAX_INT to be null, in which
case it must compare equal to NULL.

If NULL is defined as ((void*)MAX_INT), which requirement of the standard
has the implementation not met if a program cannot tell ((void*)0) apart
from ((void*)MAX_INT)?

It has certainly not met the requirement that NULL expand to a null
pointer constant. However, the only way to verify that would be by
stringizing the macro using the # operator:

#define STRING(a) STR(a)
#define STR(a) #a
#include <stdlib.h>
#include <string.h>

int main(void)
{
return strcmp(STRING(NULL), "((void*)MAX_INT)")
? EXIT_SUCCESS : EXIT_FAILURE;
}

There has been considerable disagreement expressed on comp.std.c about
whether it something really is a conformance violation, if the only way
to detect the violation is to convert the expansion of a standard macro
to a string. However, it seems to me that a strict interpretation of the
standard renders the above program strictly conforming, and guarantees
that that the program will indicate a successful termination when it is
translated and executed by any conforming implementation.
 
S

Stephen Sprunk

Wolfgang said:
I fell for the same misconception until a few months ago (when
Keith Thompson and a few others had the patience, to explain it
to me, thanks again guys). The value of a pointer in C may be
something completely different from what is actually stored in
the machine. If your architecture consideres 0xdeadbeef as a
null pointer, then the C standard states, that

void *p = something;
if( p == 0 ) { ... }

is actually compiled to a comparision of p against 0xdeadbeef.

I am now aware that (void*)0 is not necessarily the same as (void*)zero.
However, on all DOS compilers I'm aware of, NULL was all-bits zero,
and was indeed a "valid" pointer, for sufficiently loose definitions of
"valid". One could claim that the IVT was not a C "object" due to its
location, but there quite certainly was an array of pointers-to-function
at that address...

(Above assumes const int zero = 0;)

S
 
K

Keith Thompson

Francois Cartegnie said:
Just 2 cents:
"with the value defined in headers".
I guess that any well written code (not referring to 0) should work
with null redefined to MAX_INT (as long it's not in it's allocatable
address range)

As James pointed out, you meant NULL, not null, and you probably meant
((void*)INT_MAX).

I believe that redefining a macro defined in a standard header invokes
undefined behavior, but we'll ignore that for now.

What you're saying, I think, is that if a well-written program used
some unique non-null pointer value as a null pointer, then it would
still work as long as it *consistently* uses this unique value. Let's
say it defines, in some header:
#define MY_NULL ((void*)INT_MAX)
and used MY_NULL everywhere in place of NULL.

This could be made to work, but you'd have to watch out for references
to null pointers that don't use the NULL macro:

void *ptr = 0; /* sets ptr to NULL, not MY_NULL */
if (ptr) { ... } /* compares ptr to NULL, not MY_NULL */

But the biggest problem is that the standard library uses actual null
pointers in many places. You'd have to compare the result of fopen()
to NULL, but compare the result of your own foo() to MY_NULL.

The choice of which pointer representation to set aside as a null
pointer value is largely an arbitrary one, and you can change it as
long as you do so consistently. (I think that was the point you were
trying to make.) But changing it consistently requires changing the
compiler, the standard runtime library, any additional libraries
provided by the OS or by third parties, *and* your own program.
 
H

Harald van Dijk

Harald said:
[...] NULL can't legally be defined as (void*)MAX_INT, either, though
it is permissible for (void*)MAX_INT to be null, in which case it must
compare equal to NULL.

If NULL is defined as ((void*)MAX_INT), which requirement of the
standard has the implementation not met if a program cannot tell
((void*)0) apart from ((void*)MAX_INT)?

It has certainly not met the requirement that NULL expand to a null
pointer constant. However, the only way to verify that would be by
stringizing the macro using the # operator:

That's not something a strictly conforming program can do, because the
expansion is allowed to contain backslashes that do not start an escape
sequence, and the expansion is allowed to be too long to be stringised.
4000 opening parentheses, a plain old 0, and 4000 closing parentheses
make a valid null pointer constant.

[example snipped]
 
C

CBFalconer

Richard said:
.... snip ...

If we're making a pointer indeterminate, we fix it right away:

free(*old);
*old = NULL;

By adopting this strategy both in library code and in program
code, we can be reasonably confident that we won't hit any
indeterminate pointers. A test for NULL becomes adequate.

The problem with this, which we don't really notice [1], is that we
have to keep track of which pointers are secured from malloc, and
which are not. Except for NULL, the only ones that can be passed
to free are those secured via malloc and friends.

[1] I think the reason is that we are so used to it, and we tend to
keep malloced pointers in a separate mental class.
 
P

Phil Carmody

James Kuyper said:
The range of valid addresses is implementation-specific. Linux has
been successfully ported to a very wide variety of platforms; I'd be
somewhat surprised if your description of valid pointers were correct
on all of them - though I'm not ruling out that possibility.

From my limited experience of the linux kernel, most drivers (which
make up most of the bulk of the kernel code) use a RAII model, use
reference counting whereever pointers are shared, and are never in
a state where their pointers are in an indeterminate state. (They
may be invalid C pointers, but something in the structure as a whole
indicates that those pointers are indeed invalid.)

The (virtual) memory manager may be entirely different, but that's
a) a minuscule part of the elephantine kernal
b) intrinsically platform specific

Phil
 
T

Tim Rentsch

James Kuyper said:
Harald said:
[...] NULL can't legally be defined as (void*)MAX_INT,
either, though it is permissible for (void*)MAX_INT to be null, in which
case it must compare equal to NULL.

If NULL is defined as ((void*)MAX_INT), which requirement of the standard
has the implementation not met if a program cannot tell ((void*)0) apart
from ((void*)MAX_INT)?

It has certainly not met the requirement that NULL expand to a null
pointer constant. However, the only way to verify that would be by
stringizing the macro using the # operator:

#define STRING(a) STR(a)
#define STR(a) #a
#include <stdlib.h>
#include <string.h>

int main(void)
{
return strcmp(STRING(NULL), "((void*)MAX_INT)")
? EXIT_SUCCESS : EXIT_FAILURE;
}

There has been considerable disagreement expressed on comp.std.c about
whether it something really is a conformance violation, if the only way
to detect the violation is to convert the expansion of a standard macro
to a string. However, it seems to me that a strict interpretation of the
standard renders the above program strictly conforming, and guarantees
that that the program will indicate a successful termination when it is
translated and executed by any conforming implementation.

This argument uses circular reasoning. The given program is
strictly conforming only if an implementation is not allowed
to define its own null pointer constants for NULL, but that
question is the very question under consideration. Nothing
is proven one way or the other by this argument.

(I assume most people are reading 'MAX_INT' to mean INT_MAX, not that
that difference changes the comments in any material way.)
 

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,483
Members
44,902
Latest member
Elena68X5

Latest Threads

Top