Passing NULL as a function pointer

L

Lauri Alanko

char *raw_mem = (char *)0;
int *descriptors = (int *)0;
struct interrupt *ivec = (struct interrupt *)0;

A bit related <how about:

uintptr_t u = 0;
char* raw_mem = (void*) u;

To my understanding, raw_mem is not guaranteed to be a null pointer.
(Of course it's implementation-defined whether it's any other sensible
thing, either.)


Lauri
 
B

Ben Bacarisse

Joel C. Salomon said:
Not quite:

In fact, not at all...
Assume an architecture where all-zero-bits is a legitimate
memory address, but all-one-bits is a trap location & therefore the
choice for the null pointer. This below is technically undefined, but
it a likely result:

It does not look undefined to me, but that does not affect your point.
union foo_ptr {
foo *p;
uintptr_t i;
} ptr;

ptr.p = (foo *)0;
assert (ptr.i = ~( (uintptr_t)0 ));

presumably == intended
ptr.i = 0;
assert (ptr.p != NULL);

What’s happening is that ‘0’ (and equally, ‘(void *)0’) is what the
Standard calls a “null pointer constantâ€; when it converted to *any*
pointer type the result is a null pointer. And on this architecture,
the null pointer has the all-one-bits representation. If you want a
pointer to memory address 0, you need to play other games. The union
trick above might work; also

char *one = (char *)1;
void *zero = one - 1;

might be a slightly more “portable†method.

All this is true. The technique I intended to suggest was to avoid a
null pointer constant, and I failed to do so. I wanted to say:

int zero = 0;
char *zero_address = (char *)zero;
/* etc. */
 
B

Ben Bacarisse

All wrong! I should have avoided a constant 0. I think using an
integer variable does what I intended...
A bit related <how about:

uintptr_t u = 0;
char* raw_mem = (void*) u;

To my understanding, raw_mem is not guaranteed to be a null pointer.
(Of course it's implementation-defined whether it's any other sensible
thing, either.)

....as you have done here. I don't know what I was thinking.
 
E

Eric Sosman

[...]
Now the question is. If you want to get at memory address 0, how do
you? It looks like you cannot. I do not suppose I ever will need to do
that and if I did there is always assembler.

There is no guaranteed way to get at *any* specified address,
whether it be 0 or 42 or 0xDEADBEEF. From the language's point of
view, things have addresses but those addresses "belong to" the
implementation. The implementation manages the assignment of things
to addresses, not the C programmer. You can find out what address
has been given to something, but you can't control the assignment
the implementation makes. Think of an address the same way you think
of your tax ID number: You have one, and you can discover it, but you
don't get to choose your own.

That's the language, but implementations may offer their own
implementation-specific ways for you to get at particular addresses.
One way that's provided by nearly every implementation is to cast
an integer expression to a pointer of the desired type, and then
use the pointer to get at the addressed thing:

char *ptr = (char*) 0xDEADBEEF;
printf ("The char at 0xDEADBEEF is %c\n", *ptr);

Even though nearly every implementation allows this, you're not
out of the woods. The set of available addresses will vary from one
implementation to another: 0xDEADBEEF may be usable on one system
but unusable on the next, or read-write on one system but read-only
on another. Any special meaning attached to the address ("Store
0xF8 at 0xDEADBEEF to enable the MUX" or "The byte at 0xDEADBEEF
encodes the CPU temperature") is highly system-specific. You may
need system-specific calls (mmap(), for example) to make an address
available before using it this way), and other system-specific rules
and limitations are likely to apply.

You can go ahead and use these things, to the extent a particular
system allows, but be aware that you're not writing "C" but "Frobozz
Magic C for the DeathStation 9000, chip stepping 0x666." As general
advice (overridable in specific circumstances) I'd recommend that you
avoid such stuff as much as you can, or at least isolate it to small
sections of the program if you must use it (write an enable_MUX()
function instead of just poking the address from main-line code; that
way you'll have less code to change when you move to a new system).
 
J

Joel C. Salomon

The technique I intended to suggest was to avoid a
null pointer constant, and I failed to do so. I wanted to say:

int zero = 0;
char *zero_address = (char *)zero;
/* etc. */

Or, eliminating all temporaries:

void * const zero = (char *)1 - 1;

—Joel
 
B

Ben Bacarisse

Joel C. Salomon said:
Or, eliminating all temporaries:

void * const zero = (char *)1 - 1;

also I think

(char *)(int){0}

avoids any null pointer constants in the general run of things. I say
that because of the permission for an implementation to define other
forms or constant expression. I think all these techniques are plagued
by implementation-defined behaviour.
 
T

Tim Rentsch

Ben Bacarisse said:
Joel C. Salomon said:
Or, eliminating all temporaries:

void * const zero = (char *)1 - 1;

also I think

(char *)(int){0}

avoids any null pointer constants in the general run of things. I say
that because of the permission for an implementation to define other
forms or constant expression. [snip]

You can use

(char*)(0,0)

to be sure. (Still a conforming implementation could
take this as being a converted null pointer constant,
but at least it would have to issue a diagnostic if
it did.)
 
B

Ben Bacarisse

Tim Rentsch said:
Ben Bacarisse said:
Joel C. Salomon said:
On 02/19/2011 10:26 PM, Ben Bacarisse wrote:
The technique I intended to suggest was to avoid a
null pointer constant, and I failed to do so. I wanted to say:

int zero = 0;
char *zero_address = (char *)zero;
/* etc. */

Or, eliminating all temporaries:

void * const zero = (char *)1 - 1;

also I think

(char *)(int){0}

avoids any null pointer constants in the general run of things. I say
that because of the permission for an implementation to define other
forms or constant expression. [snip]

You can use

(char*)(0,0)

to be sure. (Still a conforming implementation could
take this as being a converted null pointer constant,
but at least it would have to issue a diagnostic if
it did.)

That's nice because it's C90 as well. In the same vein are

(char *)(int)(0+0.0)
(char *)(int)(float)0;

but there is a certain elegance to the comma operator version -- it uses
no more than it needs to.
 
S

sandeep

Eric said:
[...]
Now the question is. If you want to get at memory address 0, how do
you? It looks like you cannot. I do not suppose I ever will need to do
that and if I did there is always assembler.

There is no guaranteed way to get at *any* specified address,
whether it be 0 or 42 or 0xDEADBEEF.

This is one of the oldest problems in C, that the next ISO Standard
should look at fixing urgently.

The strange thing is, this is done right elsewhere in C, e.g. the use of
(int) -1 for EOF, which is different from any unsigned char (unless
CHARBIT == INTBIT).

What should happen is that a NULL pointer should be guaranteed not equal
to any valid addressable location. This should be achieved by having the
C pointer type wider than the underlying hardware type and signed, e.g.
on a 32-bit machine, C pointers would be signed 64 bits, with 0x0 through
0xffffffff mapping directly to all 2^32 hardware pointers, and (int64_t)
-1 the NULL pointer.

Just like getchar returns an int, which is wider than the "normal case"
return type to allow for outer-band signalling of anomalies
(specifically, EOF).

As it stands, on most implementations it is not possible to access memory
location 0x0, because that is indistinguishable from the NULL pointer.
 
B

Ben Pfaff

sandeep said:
What should happen is that a NULL pointer should be guaranteed not equal
to any valid addressable location. This should be achieved by having the
C pointer type wider than the underlying hardware type and signed, e.g.
on a 32-bit machine, C pointers would be signed 64 bits, with 0x0 through
0xffffffff mapping directly to all 2^32 hardware pointers, and (int64_t)
-1 the NULL pointer.

Just like getchar returns an int, which is wider than the "normal case"
return type to allow for outer-band signalling of anomalies
(specifically, EOF).

As it stands, on most implementations it is not possible to access memory
location 0x0, because that is indistinguishable from the NULL pointer.

Nice troll.
 
E

Eric Sosman

Eric said:
[...]
Now the question is. If you want to get at memory address 0, how do
you? It looks like you cannot. I do not suppose I ever will need to do
that and if I did there is always assembler.

There is no guaranteed way to get at *any* specified address,
whether it be 0 or 42 or 0xDEADBEEF.

This is one of the oldest problems in C, that the next ISO Standard
should look at fixing urgently.

That seems rather limiting, even short-sighted. Why do you think
it would be a good idea for the C language Standard to dictate what a
machine's addressing scheme looks like? The FAQ mentions a machine on
which addresses were not numbers at all, but ordered pairs of "object,
offset;" do you think C should refuse to be implementable on such a
machine?
The strange thing is, this is done right elsewhere in C, e.g. the use of
(int) -1 for EOF, which is different from any unsigned char (unless
CHARBIT == INTBIT).

EOF is very commonly -1, but the Standard requires only that it
be a negative int, any negative int. (In fact, the value CHAR_MIN-1
What should happen is that a NULL pointer should be guaranteed not equal
to any valid addressable location.

It is already the case that a null pointer (not "NULL pointer")
compares unequal to a pointer to any actual variable, function, or
allocated memory area. If you want to go further than that, you'll
have to explain what a "valid addressable location" is. Try to do
so without jettisoning unusual addressing schemes unnecessarily.
As it stands, on most implementations it is not possible to access memory
location 0x0, because that is indistinguishable from the NULL pointer.

So what? What is so special about the memory at address zero?
"That's where the interrupt vector lives. Or is it the master reset
point? No, it's the CPU's finagle vector, except that it's only for
Core Zero of CPU Zero. Oh, did you mean physical address zero, or
virtual address zero, or logical address zero, or delegated address
zero?" And so on, and so on. Whatever special significance attaches
to address zero or address 0xDEADBEEF or address <t,808> is a matter
for the system's builders, not a matter for a language Standard.

The Standard allows you to convert a number to a pointer, and says
the result of doing so is "implementation-defined." What more do you
want? If the Standard gave you a portably-defined conversion (somehow)
but still said that the effect of using the pointer was implementation-
defined, what would you have gained?
 
M

Michael Press

Keith Thompson said:
Yes, that will likely silence the warning -- but keep in mind
that it's a workaround for a compiler bug. (I don't mean
a "bug" in the sense that the compiler is necessarily non-conforming,
just that the warning is spurious.)

You do generally need to cast NULL to the expected pointer type when
calling variadic functions, but that's not what's happening here; the
compiler wouldn't know the expected type.

A couple of questions for the OP:

Does the compiler you're using claim to be conforming? If not, what
does its documentation say about the use of function pointers?

Since you didn't show us the full source of the program, we can't be
100% sure of what's going on. What happens when you compile this?

typedef void (*funcptr)(void);

void func(funcptr arg)
{
if (arg) {
arg();
}
}

int main(void)
{
func((void*)0);
return 0;
}

One detail arose when I fooled around with this
that probably bears mentioning.

Both func((void*)0); func(0); compiled without warning, but

func((int (*)(void))0);

generated

warning: passing argument 1 of 'func' from incompatible pointer type
 
B

Ben Bacarisse

Michael Press said:
Keith Thompson <[email protected]> wrote:

One detail arose when I fooled around with this
that probably bears mentioning.

Both func((void*)0); func(0); compiled without warning, but

func((int (*)(void))0);

generated

warning: passing argument 1 of 'func' from incompatible pointer type

That's as I'd expect. (int (*)(void))0 is a null pointer but it is of
the wrong type to be the argument of the function call. When there is a
prototype in scope, function arguments are converted "as if by
assignment" but a value of type (int (*)(void)) can't be assigned to an
object of type (void (*)(void)).

Both 0 and (void *)0 are null pointer constants, and a NPC is can always
be assigned to an lvalue of any pointer type. This comes from the rules
of pointer conversion and from the description of simple assignment.
 
J

J. J. Farrell

Ben said:
Nice troll.

Not really, since this one finally makes it certain that he's a troll; a
nice troll leaves you with some nagging doubt. Up to now I'd been
undecided between fool and troll.
 
S

sandeep

J. J. Farrell said:
Not really, since this one finally makes it certain that he's a troll; a
nice troll leaves you with some nagging doubt. Up to now I'd been
undecided between fool and troll.

Dear Mr Farrell & Mr Pfaff:

You may disagree with what I say but please respect my right to say it.

I need to build up a portfolio of suggested improvements to the ISO
Standard for my masters thesis. Some of them may have unforeseen
problems, but all of them have my thought put in to them. I am happy if
people seek to improve them, unfortunately on this group most people only
want to knock down ideas.

You may not be aware that on most architectures, truncating a 64-bit
value to 32-bits will be a very cheap operation, often even free, e.g.
the low half of a 64-bit register can be accessed directly as a 32-bit
register with no transformations needed. So my proposal has small/zero
cost and allows access to address 0x0, often needed for interrupt tables.
 
K

Keith Thompson

sandeep said:
Dear Mr Farrell & Mr Pfaff:

You may disagree with what I say but please respect my right to say it.

Of course you have a right to say whatever you like; nobody has proposed
sensoring you.
I need to build up a portfolio of suggested improvements to the ISO
Standard for my masters thesis. Some of them may have unforeseen
problems, but all of them have my thought put in to them. I am happy if
people seek to improve them, unfortunately on this group most people only
want to knock down ideas.

Most people here would love to see new ideas that actually make sense.
You have yet to offer any. This is not about you personally; you just
keep offering bad ideas.
You may not be aware that on most architectures, truncating a 64-bit
value to 32-bits will be a very cheap operation, often even free, e.g.
the low half of a 64-bit register can be accessed directly as a 32-bit
register with no transformations needed. So my proposal has small/zero
cost and allows access to address 0x0, often needed for interrupt tables.

Your proposal has the cost of doubling the amount of storage needed to
store pointers in memory. Think about the amount of memory needed to
store a large linked list, binary tree, or other dynamic structure.

On any system where accessing address 0x0 (physical? virtual? whatever),
there will almost certainly already be a (non-portable) way to do so.
The lack of portability is not a problem; accessing address 0x0 is an
inherently non-portable operation. The following is one plausible
way to do it:

int zero = 0;
unsigned char *p = (unsigned char*)zero;
unsigned char result = *p;

(Add volatile keywords as needed.)

You are offering an expensive solution for a nonexistent problem,
and you propose (if I understand you correctly) to make this solution
mandatory for all conforming implementations. Do you still think
it's worth the cost?

I am not (yet) assuming that you are a deliberate troll. You might
actually think that your ideas are good ones. But please stop and think
for a moment about the criticisms that have been offered.
 
E

Eric Sosman

[...]
You may disagree with what I say but please respect my right to say it.

I respect the right. I do not, however, respect your failure
to make good use of it.
I need to build up a portfolio of suggested improvements to the ISO
Standard for my masters thesis. Some of them may have unforeseen
problems, but all of them have my thought put in to them.

If this is a sample of your thought, I suggest -- and this is
entirely serious, not meant as an insult -- that you seek another field
of endeavor. Your thought, earnest and well-intentioned though it may
have been, is clearly unsuited to the disciplines of programming for
digital computers. I predict that your quest to write a thesis that
will earn you a degree will be futile; you lack the ability.

That's not a Bad Thing in and of itself. My clumsy fingers and
generally deficient dexterity would make me an atrocious harpist, but
I console myself with the thought that I'm good at other things. My
inability to play the harp would only become Bad if I forced my wretched
twangings on the public at large, if I deluded myself into thinking I
merited advanced degrees in harpistry. Nobody despises me today for
failing to play the harp, but if I were to start trying to pass myself
off as a harpist I would deserve their scorn.
I am happy if
people seek to improve them, unfortunately on this group most people only
want to knock down ideas.

It does no one a service to applaud ill-considered ideas.
You may not be aware that on most architectures, truncating a 64-bit
value to 32-bits will be a very cheap operation, often even free, e.g.
the low half of a 64-bit register can be accessed directly as a 32-bit
register with no transformations needed. So my proposal has small/zero
cost and allows access to address 0x0, often needed for interrupt tables.

... and here again you demonstrate an inability to reason about your
chosen field. Think for a moment, Sandeep: Imagine, say, a binary tree
whose nodes hold three pointers: One to the payload, and one to each of
the left and right subtrees. Pretty standard stuff, nothing unusual at
all. Okay, your proposal would double the size of this data structure,
from (say) twelve to twenty-four bytes, or maybe twenty-four to forty-
eight. Now, aside from I/O devices, what is the slowest part of a
modern computer? Hint: It's the part with all those multiple levels of
expensive cache between it and the CPU. And you want to double the
amount of data stored in this slowest part of the system, double the
pressure on every level of cache, double the miss rates thereof -- and
you want to call this "very cheap?" You want to store three bits'
worth of information in ninety-six bits of memory, or maybe a hundred
ninety-two, and claim this is "often even free?"

Find a harp, Sandeep. Or put your fine motor skills to use as a
dentist or surgeon, or give rein to your killer instinct as a hired
assassin, or discover a talent for hybridizing tea plants. But stop
messing with computers; your computer engineering is badly out of tune.
 
T

Tim Rentsch

Ben Bacarisse said:
Tim Rentsch said:
Ben Bacarisse said:
On 02/19/2011 10:26 PM, Ben Bacarisse wrote:
The technique I intended to suggest was to avoid a
null pointer constant, and I failed to do so. I wanted to say:

int zero = 0;
char *zero_address = (char *)zero;
/* etc. */

Or, eliminating all temporaries:

void * const zero = (char *)1 - 1;

also I think

(char *)(int){0}

avoids any null pointer constants in the general run of things. I say
that because of the permission for an implementation to define other
forms or constant expression. [snip]

You can use

(char*)(0,0)

to be sure. (Still a conforming implementation could
take this as being a converted null pointer constant,
but at least it would have to issue a diagnostic if
it did.)

That's nice because it's C90 as well. In the same vein are

(char *)(int)(0+0.0)
(char *)(int)(float)0;

but there is a certain elegance to the comma operator version -- it uses
no more than it needs to.

More pointedly, these last examples could be accepted (without a
diagnostic message) by a conforming implementation as being null
pointers. The rule for the comma operator is a constraint violation;
the provisions for floating point operands are covered only under
Semantics, so they could be accepted as integer constant expressions
without a diagnostic.
 
T

Tim Rentsch

[snip]

(void *) 0 is guaranteed to represent a null pointer constant, whether
or not it is represented as a 32-bit value with the pattern 0xdeadbeef.
The assembly language implementing "if (p == NULL) { ... }
might include
cmpl r4,#0xdeadbeef
jne .L55
... code within the braces ...
.L55:

(void *) 0xdeadbeef or (void *) 0xffffffff might or might represent
the same null pointer constant.

They might or might not represent the same null _pointer_.
It is highly unlikely that they are null pointer _constants_
(that is assuming the Standard even allows such expressions
to be NPC's, which is itself open to debate).
 

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

Staff online

Members online

Forum statistics

Threads
474,085
Messages
2,570,597
Members
47,218
Latest member
GracieDebo

Latest Threads

Top