Function Pointers and NULL/0

S

Stephan Schulz

Hi All,

I have a nice (and working) program that contains a NULL-terminated
array of function pointers:

static ClausePrioFun prio_fun_array[]=
{
PrioFunPreferGroundGoals,
PrioFunPreferUnitGroundGoals,
...
PrioFunPreferWatchlist,
PrioFunDeferWatchlist,
NULL
};

....where ClausePrioFun is defined as

typedef EvalPriority (*ClausePrioFun)(Clause_p clause);

and all the other types are well-defined as well.

The program has compiled (and worked) fine for years on a large number
of OS/Compiler combinations.

Now it is being ported much more widely, and one compiler (that I do
not have direct access to) reports

/opt-ext/studio90.d/SUNWspro/bin/cc -c -o che_prio_funs.o
-O -xarch=v9 -DNDEBUG -DCONSTANT_MEM_ESTIMATE -DFAST_EXIT
che_prio_funs.c
"che_prio_funs.c", line 100: warning: initialization type mismatch

where line 100 is the line with the NULL above.

Now, I suspect that NULL is defined as ((void*)0) on that platform,
and I know that void* is not guranteed to be auto-castable to a
function pointer type. My suspicion is that that causes the warning.

However, I wonder if there is a portable, standard way to get the
function pointer equivalent of NULL, i.e. a pointer that is considered
as logically false in if(),&&,||,!, and that is different from all
pointers that point somewhere sensible.

My first guess would be to just use 0, the second to explicitely cast
0 or NULL to ClausePrioFun.

Neither is totally convincing. I checked the FAQ, but either was blind
or it does not contain anything useful.

I'm thankful for any help...

Bye,

Stephan
 
E

Eric Sosman

Stephan said:
Hi All,

I have a nice (and working) program that contains a NULL-terminated
array of function pointers:

static ClausePrioFun prio_fun_array[]=
{
PrioFunPreferGroundGoals,
PrioFunPreferUnitGroundGoals,
...
PrioFunPreferWatchlist,
PrioFunDeferWatchlist,
NULL
};

...where ClausePrioFun is defined as

typedef EvalPriority (*ClausePrioFun)(Clause_p clause);

and all the other types are well-defined as well.

The program has compiled (and worked) fine for years on a large number
of OS/Compiler combinations.

Now it is being ported much more widely, and one compiler (that I do
not have direct access to) reports

/opt-ext/studio90.d/SUNWspro/bin/cc -c -o che_prio_funs.o
-O -xarch=v9 -DNDEBUG -DCONSTANT_MEM_ESTIMATE -DFAST_EXIT
che_prio_funs.c
"che_prio_funs.c", line 100: warning: initialization type mismatch

where line 100 is the line with the NULL above.
[...]

Try using `(ClausePrioFun)NULL' as the final initializer.
This is not guaranteed to silence every warning from every
compiler (the compiler is allowed to prattle as much as it
likes, so long as it accepts correct programs), but stands a
reasonable chance of doing so.

(Disclaimer: You're using a Sun compiler and I work
for Sun, but that's just coincidence. I'm as likely to be
dead wrong in this message as in any other, and the dog
ate my infallibility license.)
 
I

Ian Pilcher

Stephan said:
However, I wonder if there is a portable, standard way to get the
function pointer equivalent of NULL, i.e. a pointer that is considered
as logically false in if(),&&,||,!, and that is different from all
pointers that point somewhere sensible.

I don't believe that there's a guaranteed portable way to do this.
After all, nothing in the standard says that an all-zero bit pattern
can't represent the location of a real function. (Slightly less
unlikely, all function pointers on some platform might contain some
non-zero bits that identify their type, causing a comparison to 0 to
always fail.)

For true compliance, I believe that you need to create your own special
value:

EvalPriority PrioNULL(Clause_p clause)
{
abort();
}

Obviously, you'll have to specifically test for equality to this value,
rather than 0.

If, OTOH, you're willing to "limit" your code to POSIX/UNIX-ish and
Microsoft platforms:

#define PRIO_NULL ((ClausePrioFun)0)
 
S

Stephan Schulz

I don't believe that there's a guaranteed portable way to do this.
After all, nothing in the standard says that an all-zero bit pattern
can't represent the location of a real function.

But then the standard also does not sat that an all-zero bit pattern
can represent the location of a real object. What representation NULL
(pointer context 0) has, is entirely up to the implementation. How
NULL behaves, on the other hand, is not. I'm looking for FUNCTION_NULL
or something like that...
(Slightly less
unlikely, all function pointers on some platform might contain some
non-zero bits that identify their type, causing a comparison to 0 to
always fail.)

But I don't compare to 0 (or NULL (or anything all-bits 0)).
For true compliance, I believe that you need to create your own special
value:

EvalPriority PrioNULL(Clause_p clause)
{
abort();
}

Obviously, you'll have to specifically test for equality to this value,
rather than 0.

That is ugly!
If, OTOH, you're willing to "limit" your code to POSIX/UNIX-ish and
Microsoft platforms:

#define PRIO_NULL ((ClausePrioFun)0)

That is what I'm currently do, although I follow the suggestion of
Eric Sosman upstream, of casting the concrete case.

Thanks anyways!

Bye,

Stephan
 
A

Andrey Tarasevich

P.J. Plauger said:
...

It's worse than that. If NULL is indeed defined as (void*)0, it's
not even *explicitly* castable to a function pointer. Object pointers
and function pointers are incommensurate. Older compilers tend to be
a bit too tolerant on this point, however.

That's not true. Both C89/90 and C99 define null pointer constant as an
integral constant expression with the value 0 or such an expression cast
to type 'void*'. The compiler is required to recognize '(void*) 0' as
null pointer constant and compile the code accordingly. There's
absolutely no reason for any diagnostic messages in this case. The last
compiler used by the OP is broken.
Yes, that works, provided the context is clear enough (as it is
in the case you present).


(ClausePrioFun)0 also works, and has the twin advantages of
being self-documenting and usable in all contexts, including
as an argument to a function with a varying number of arguments
(or no prototype, heaven forbid).

(ClausePrioFun)NULL should be diagnosed as an error on any
implementation that defines NULL as (void*)0, for the reason
given above.
...

Not true. For the reason given above.
 
A

Andrey Tarasevich

Stephan said:
...
I have a nice (and working) program that contains a NULL-terminated
array of function pointers:

static ClausePrioFun prio_fun_array[]=
{
PrioFunPreferGroundGoals,
PrioFunPreferUnitGroundGoals,
...
PrioFunPreferWatchlist,
PrioFunDeferWatchlist,
NULL
};

...where ClausePrioFun is defined as

typedef EvalPriority (*ClausePrioFun)(Clause_p clause);

and all the other types are well-defined as well.

The program has compiled (and worked) fine for years on a large number
of OS/Compiler combinations.

Now it is being ported much more widely, and one compiler (that I do
not have direct access to) reports

/opt-ext/studio90.d/SUNWspro/bin/cc -c -o che_prio_funs.o
-O -xarch=v9 -DNDEBUG -DCONSTANT_MEM_ESTIMATE -DFAST_EXIT
che_prio_funs.c
"che_prio_funs.c", line 100: warning: initialization type mismatch

where line 100 is the line with the NULL above.

Now, I suspect that NULL is defined as ((void*)0) on that platform,
and I know that void* is not guranteed to be auto-castable to a
function pointer type. My suspicion is that that causes the warning.
...

This only means that that last compiler is broken. The C language
specification (both C89/90 and C99) specifically requires compilers to
recognize integral constant expressions with value 0 cast to type
'void*' as null pointer constants (NPCs). In other words, the '(void*)
0' is exempt from the "non-castability" rule you mention above.
Apparently, that complaining compiler in not aware of that rule. Try
looking for a newer version of the compiler or switch to using an
explicit '0' as NPC.
 
I

Ian Pilcher

Stephan said:
But then the standard also does not sat that an all-zero bit pattern
can represent the location of a real object. What representation NULL
(pointer context 0) has, is entirely up to the implementation. How
NULL behaves, on the other hand, is not. I'm looking for FUNCTION_NULL
or something like that...

Pay no attention to my earlier ramblings. I forgot about 6.3.2.3(3):

An integer constant expression with the value 0, or such an expression
cast to type void *, is called a null pointer constant.(55) If a null
pointer constant is converted to a pointer type, the resulting
pointer, called a null pointer, is guaranteed to compare unequal to a
pointer to any object or function.

(55) The macro NULL is defined in <stddef.h> (and other headers) as a
null pointer constant; see 7.17.

So converting either 0 or (void *)0 to a function pointer will give you
a null pointer constant. An explicit cast should silence any compiler
warnings.

6.3.2.3(4) is also of interest:

Conversion of a null pointer to another pointer type yields a null
pointer of that type. Any two null pointers shall compare equal.

So testing a function pointer for equality to (void *)0 is definitely
OK. Testing for equality to 0 should also be OK; a pointer cannot be
directly compared to an integer (6.5.9), so the compiler should treat
the 0 as a null pointer constant.
But I don't compare to 0 (or NULL (or anything all-bits 0)).

if, &&, ||, and ! are all defined in terms of comparison to zero. See
6.8.4.1, 6.5.13, 6.5.14, and 6.5.3.3 respectively. This is the only
reason that they can be used with pointers.

Sorry for the earlier noise.
 
T

Tim Rentsch

Hi All,

I have a nice (and working) program that contains a NULL-terminated
array of function pointers:

static ClausePrioFun prio_fun_array[]=
{
PrioFunPreferGroundGoals,
PrioFunPreferUnitGroundGoals,
...
PrioFunPreferWatchlist,
PrioFunDeferWatchlist,
NULL
};

...where ClausePrioFun is defined as

typedef EvalPriority (*ClausePrioFun)(Clause_p clause);

and all the other types are well-defined as well.

The program has compiled (and worked) fine for years on a large number
of OS/Compiler combinations.

Now it is being ported much more widely, and one compiler (that I do
not have direct access to) reports

/opt-ext/studio90.d/SUNWspro/bin/cc -c -o che_prio_funs.o
-O -xarch=v9 -DNDEBUG -DCONSTANT_MEM_ESTIMATE -DFAST_EXIT
che_prio_funs.c
"che_prio_funs.c", line 100: warning: initialization type mismatch

where line 100 is the line with the NULL above.

Now, I suspect that NULL is defined as ((void*)0) on that platform,
and I know that void* is not guranteed to be auto-castable to a
function pointer type. My suspicion is that that causes the warning.

However, I wonder if there is a portable, standard way to get the
function pointer equivalent of NULL, i.e. a pointer that is considered
as logically false in if(),&&,||,!, and that is different from all
pointers that point somewhere sensible.

My first guess would be to just use 0, the second to explicitely cast
0 or NULL to ClausePrioFun.

Neither is totally convincing. I checked the FAQ, but either was blind
or it does not contain anything useful.

If you look at 6.2.5 p20 (defining pointer types) and 6.3.2.3 p3
(defining null pointer constants), it seems clear that both '0' and
'(void*)0' are null pointer constants that may be converted to any
pointer type, and that includes pointers to function. The rules for
simple assignment (6.5.16.1) and initializtion (6.7.8) seem to imply
that null pointer constants may be used for initializing function
pointer arrays, although the language doesn't plainly say this
outright (at least, not to my slightly-more-than-cursory reading).
In any case that seems to be the intent.

A good guess is that what's happening is this. The compiler sees the
'(void*)0' definition of NULL, and, even if it knows that the
expression is a null pointer constant, also determines the type of the
expression to be 'void *', which might merit a warning when used to
specify a value for a function pointer. And the warning is arguably
useful too, but let's not get into that now.

So even though the given program text as written is (apparently)
conformant and portable, the compiler warning makes us want to choose
another form of expression. If it were me, I would use -- depending
on local practices and preferences -- one of:

0

or

(ClausePrioFun) 0

or

#define NULL_F (0)
...

NULL_F

or perhaps

#define NULL_FUNCTION(t) ((t)0)
...

NULL_FUNCTION( ClausePrioFun )

all of which I believe are conformant and portable, and at least
some of which I would exprect to be warning free.

I'm thankful for any help...

You are most welcome!
 
L

Lawrence Kirby

Pay no attention to my earlier ramblings. I forgot about 6.3.2.3(3):

An integer constant expression with the value 0, or such an expression
cast to type void *, is called a null pointer constant.(55) If a null
pointer constant is converted to a pointer type, the resulting
pointer, called a null pointer, is guaranteed to compare unequal to a
pointer to any object or function.

(55) The macro NULL is defined in <stddef.h> (and other headers) as a
null pointer constant; see 7.17.

So converting either 0 or (void *)0 to a function pointer will give you
a null pointer constant. An explicit cast should silence any compiler
warnings.

Just to be clear 0 and (void *)0 ARE null pointer constants. Converting
either to a function pointer type will give you a null pointer of that
type and is NOT a null pointer constant.

No cast is required. Compiler warnings in this case don't make much sense.
Since it is unclear why the compiler is warning it is unclear whether an
explicit cast will silence it.
6.3.2.3(4) is also of interest:

Conversion of a null pointer to another pointer type yields a null
pointer of that type. Any two null pointers shall compare equal.

So testing a function pointer for equality to (void *)0 is definitely
OK. Testing for equality to 0 should also be OK; a pointer cannot be
directly compared to an integer (6.5.9), so the compiler should treat
the 0 as a null pointer constant.

Yes, this is fine. 0 is a null pointer constant, although this property is
only significant in pointer contexts. Comparison of any (valid) pointer
value against any null pointer constant is well defined.

Lawrence
 

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
474,159
Messages
2,570,888
Members
47,420
Latest member
ZitaVos505

Latest Threads

Top