Passing NULL as a function pointer

P

pozz

I have a function that has a function pointer as one of its argument.
Can I pass NULL as this parameter?

I have some code (from lwip project - www.sics.se/~adam/lwip) that
often pass NULL as function pointer parameter, but my compiler (for 16
bits Fujtsu 16LX series microcontrollers) gives a warning for that.
For example:
argument passing incompatible pointer types:
expected `tcp_accept_fn' actual `void *': argument 2 of
`tcp_accept'
 
D

Douglas

I have a function that has a function pointer as one of its argument.
Can I pass NULL as this parameter?
[...]

Hmm... well, dereferencing a null pointer is undefined behavior.
So, probably not. Of coarse, OTOH I'm fairly new here.
 
J

James Kuyper

I have a function that has a function pointer as one of its argument.
Can I pass NULL as this parameter?
[...]

Hmm... well, dereferencing a null pointer is undefined behavior.
So, probably not. Of coarse, OTOH I'm fairly new here.

He didn't say that the pointer would be de-referenced. When part of a
function's behavior is optional in a way that is connected to one of
it's pointer parameters, it's fairly commonplace to use a null pointer
argument of that type to indicate that the optional behavior should be
skipped.
 
R

Roberto Waltman

pozz said:
... my compiler (for 16 bits Fujtsu 16LX series microcontrollers) gives a warning for that.
For example:
argument passing incompatible pointer types:
expected `tcp_accept_fn' actual `void *': argument 2 of
`tcp_accept'

In addition to what others wrote, that warning complains about
incompatible types, not about the pointer being NULL.
If you want a "clean compile", casting NULL into the type of function
pointer expected will squelch the warning.
 
K

Keith Thompson

Roberto Waltman said:
In addition to what others wrote, that warning complains about
incompatible types, not about the pointer being NULL.
If you want a "clean compile", casting NULL into the type of function
pointer expected will squelch the warning.

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;
}
 
M

Michael Angelo Ravera

I have a function that has a function pointer as one of its argument.
Can I pass NULL as this parameter?

I have some code (from lwip project -www.sics.se/~adam/lwip) that
often pass NULL as function pointer parameter, but my compiler (for 16
bits Fujtsu 16LX series microcontrollers) gives a warning for that.
For example:
  argument passing incompatible pointer types:
    expected `tcp_accept_fn' actual `void *': argument 2 of
`tcp_accept'

Of course, if a few CPU cycles won't kill you, it's always better to
pass a pointer to properly shaped function that does nothing.
 
K

Keith Thompson

Michael Angelo Ravera said:
Of course, if a few CPU cycles won't kill you, it's always better to
pass a pointer to properly shaped function that does nothing.

Why is that always better? If the function is specified to do nothing
if the passed function pointer is null, what's wrong with passing a null
pointer (assuming the compiler doesn't issue a spurious diagnostic)?
 
P

pozz

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.)

Yes, I think I'll use this workaround, just to silence this warning.

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

Nothing special.

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?
[...]

The warning comes again out...
 
J

Joel C. Salomon

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?
[...]

The warning comes again out...

That seems to be non-standard behavior.

What happens if you compile Keith’s example program, but with the line
func((void*)0);
replaced with
func(0);
— does that work better?

According to the standard (§6.3.2.3 p3), the following holds:

int main(void)
{
func(0); // should work
func((void*)0); // should also work, but your system is non-standard
func(NULL); // resolves to either of the foregoing; should work
void *null = 0;
func(null); // should *not* work
func((funcptr)null); // often permitted (common extension §J.5.7),
// but not portable, nor guaranteed to work as
// expected even if it compiles
return 0;
}

Both “0†and “(void*)0†are “null pointer constants†and *should* convert to
null pointers or null function pointers as required. You might need to override
your system’s definition of NULL from “(void*)0†to “0†to compensate for its
non-standard behavior. Probably a better idea than introducing casts all over.

—Joel
 
K

Keith Thompson

Joel C. Salomon said:
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?
[...]

The warning comes again out...

That seems to be non-standard behavior.

It doesn't *violate* the standard. Compilers can issue any additional
warnings they like. They just have to generate correctly working code
for programs that don't violate any syntax rules or constraints, issue
diagnostics for any violations of any syntax rules or constraints, and
refuse to compile anything with an active #error directive.

The warning about a valid construct does make me worry a little about
whether the compiler is generating correct code for it, though.
 
A

Andrey Tarasevich

I have a function that has a function pointer as one of its argument.
Can I pass NULL as this parameter?

I have some code (from lwip project - www.sics.se/~adam/lwip) that
often pass NULL as function pointer parameter, but my compiler (for 16
bits Fujtsu 16LX series microcontrollers) gives a warning for that.
For example:
argument passing incompatible pointer types:
expected `tcp_accept_fn' actual `void *': argument 2 of
`tcp_accept'

Apparently on your platform NULL is defined as `(void *) 0` or something
similar (key part being the `void *` type). It is perfectly valid to use
NULL declared this way for initializing function pointers, but lower
quality compilers might not be smart enough the realize that. Hence the
warning.

You can avoid the warning by passing `0` instead of NULL.
 
K

Keith Thompson

pozz said:
Il 18/02/2011 20:19, Andrey Tarasevich ha scritto:

Yes, indeed in the past I passed '0' instead of NULL for function
pointers argument, but I don't like this approach anymore...

Just to avoid any possible misunderstanding, we're talking about
0
an integer constant, not
'0'
a character constant.
 
K

Keith Thompson

pozz said:
Il 18/02/2011 17:02, Joel C. Salomon ha scritto:

Yes, in this way the compiler compiles without any warning.

So the compiler recognizes 0, but not (void*)0, as a null pointer
constant.

Can you submit a bug report? Again, the behavior you've told us
about isn't necessarily non-conforming, but I am concerned that,
if the compiler warns about this valid code, it might be handling
it incorrectly. And even if that's not the case, the warning is
spurious.
Yes, it is another good idea, thank you for your suggestion.

Overriding declarations in your implementations standard headers
strikes me as a very risky thing to do, unless you have a *very*
good reason to do so *and* you're rightly confident that you know
the situation better than the implementers do.
 
R

Richard

Apparently on your platform NULL is defined as `(void *) 0` or something
similar (key part being the `void *` type). It is perfectly valid to use
NULL declared this way for initializing function pointers, but lower
quality compilers might not be smart enough the realize that. Hence the
warning.

You can avoid the warning by passing `0` instead of NULL.

I have not seen a compiler that did not have the NULL value set to
zero (Not confusing nul and NULL) or a cast as a pointer.

My understanding of NULL is that it is pointer value that will never
be in the addressing range of your program. The NULL value is up to
the compiler (and may be dictated by the hardware). There is no
guarantee that NULL will be a cast of any particular value.

Other C compilers could use 0xffffffff as NULL rather than 0. Using a
numeric value could break code on other compilers.
 
A

Anders Wegge Keller

Richard said:
the compiler (and may be dictated by the hardware). There is no
guarantee that NULL will be a cast of any particular value.

I'm pretty certain that whatever bit pattern the implementation uses
as representation for NULL, it will have to be equal to (void *) 0. I
don't have the standard handy, and I cannot remeber if it's
assignment, comparision or both that's mentioned.
 
E

Eric Sosman

I have not seen a compiler that did not have the NULL value set to
zero (Not confusing nul and NULL) or a cast as a pointer.

My understanding of NULL is that it is pointer value that will never
be in the addressing range of your program. The NULL value is up to
the compiler (and may be dictated by the hardware). There is no
guarantee that NULL will be a cast of any particular value.

Other C compilers could use 0xffffffff as NULL rather than 0. Using a
numeric value could break code on other compilers.

You're mixing up two or maybe three different things: The run-
time value of a null pointer, the representation of that value as
a batch of bytes, and the source-code construct that produces it.
In an attempt to dispel some confusion:

- `NULL' is an identifier, a source-code construct. Various
Standard headers define the name as a macro, also a source-
code construct.

- The `NULL' macro defined by the Standard headers expands to
a "null pointer constant," which is "an integer constant
expression with the value 0, or such an expression cast to
type void *". Expressions, constant expressions, and casts
are also source-code constructs.

- (Why do I keep harping on "source-code construct?" Because of
the confusion between the way a value is "spelled" in source
code and the way that value "actually looks" at run-time. When
you write '\n' in source, do you imagine that the corresponding
run-time value involves two quote marks, a back-slash, and an n?
Just so with 0 or (void*)0: They are the source code's way of
denoting a value, not images of the value.

- Onward: When they appear in pointer contexts, 0 and (void*)0
create null pointer values in the run-time program (but see
below). These values have unspecified bit patterns (plural
intended), at the implementation's whim. Many implementations
use all-bits-zero-for-however-many-bytes-it-requires as a null
pointer value, but this is not obligatory; the FAQ mentions
some systems that use(d) other representations.

- Even when a null pointer constant appears in the source code,
it is not guaranteed that a null pointer value appears in the
run-time program. For example, `if (p == NULL) return;' might
produce code like `ld r0,p; tst r0; jz return_point;' -- and
where in this code is there a bit pattern representing a null
pointer value?

- Finally, to the null pointer value itself. No matter how it
is represented in the machine, we know (1) all null pointer
values compare equal to each other, and (2) all null pointer
values compare *un*equal to pointers to any variable or function
or allocated memory area. We need not (and should not) appeal
to notions of an "addressing range" to explain how this is made
to work; as C programmers all we need to know (and all we should
rely on) is that it *does* work, somehow. An implementor, of
course, has the burden of making it work and thus must care about
the chosen mechanisms, just as he must care about how `static'
variables get their initial values. As programmers, we should
fret about the former only as much as we do about the latter.

If this seems pedantic, that's only because it *is* pedantic.
But there's so much confusion about null pointers -- the FAQ devotes
an entire section to the topic -- that perhaps a small helping of
pedantry is called for.
 
J

James Kuyper

I'm pretty certain that whatever bit pattern the implementation uses
as representation for NULL, it will have to be equal to (void *) 0. I
don't have the standard handy, and I cannot remeber if it's
assignment, comparision or both that's mentioned.

"be equal to" inherently implies comparison, not assignment.

(void*)0 is a null pointer constant. The expansion of the NULL macro is
also required to be one. Since (void*)0 is an expression with a null
pointer value, in the expression:

NULL==(void*)0

whatever expression NULL expands to will also be converted to a null
pointer value with type 'void*', whether or not NULL itself has a
pointer type. All null pointers of the same type must compare equal. So,
yes it is guaranteed.
 
R

Richard

I'm pretty certain that whatever bit pattern the implementation uses
as representation for NULL, it will have to be equal to (void *) 0. I
don't have the standard handy, and I cannot remeber if it's
assignment, comparision or both that's mentioned.


Thanks Anders, James and Eric

My (miss)understanding of this is rather old and goes back to
something I read that made sense at the time.

What you have said and http://www.lysator.liu.se/c/c-faq/c-1.html have
made me change my mind.


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.
 
B

Ben Bacarisse

Richard said:
Thanks Anders, James and Eric

My (miss)understanding of this is rather old and goes back to
something I read that made sense at the time.

What you have said and http://www.lysator.liu.se/c/c-faq/c-1.html have
made me change my mind.


Now the question is. If you want to get at memory address 0, how do
you? It looks like you cannot.

No, it's easy (provided it is permitted by the implementation, of
course). You can't use (void *)0 because that has a special meaning,
but then you can access anything using a void pointer anyway. The key
is to start off with the right type of pointer for whatever you expect
to find at the zero address:

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

Note that you can't omit the casts or you will run into the problem
you'd identified.

<snip>
 
J

Joel C. Salomon

No, it's easy (provided it is permitted by the implementation, of
course). You can't use (void *)0 because that has a special meaning,
but then you can access anything using a void pointer anyway. The key
is to start off with the right type of pointer for whatever you expect
to find at the zero address:

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

Note that you can't omit the casts or you will run into the problem
you'd identified.

Not quite: 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:

union foo_ptr {
foo *p;
uintptr_t i;
} ptr;

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

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.

—Joel
 

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,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top