Reputation: 215287
C allows NULL
to be defined to any null pointer constant, in other words, any integer constant expression that evaluates to 0, or such an expression cast to void *
. My question concerns whether the choice of definition really matters, i.e. whether an otherwise-correct program might depend on which definition is used. For the purpose of this question, I'd like to ignore issues like NULL
being passed to variadic functions or functions lacking prototypes, since I've already dealt with it separately. Let's assume sizeof NULL == sizeof(void *)
and sizeof NULL == sizeof(T)
for some integer type T
, so that sizeof
is not sufficient to answer the question of whether NULL
has pointer type.
Obviously, C11 provides a way to distinguish the type of NULL
or any other expression: the _Generic
keyword.
C99 also provides one obscure way that seems to be reliable:
int null_has_ptr_type()
{
char s[1][1+(int)NULL];
int i = 0;
return sizeof s[i++], i;
}
Are there any other methods by which the type of NULL
may be determined by a conforming C program? Any that work in C89?
Upvotes: 17
Views: 1371
Reputation: 239081
Here's one obscure way: If the program uses the expression &*NULL
, this won't compile with NULL
having integer type but will if NULL
has pointer type.
C99 has wording for this special case:
If the operand [of the
&
operator] is the result of a unary*
operator, neither that operator nor the&
operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.
The constraints on the operators aren't violated: the operand of &
is the result of the unary *
operator, and the operand of the unary *
operator has pointer type (as we are assuming NULL
is defined that way).
Upvotes: 1
Reputation: 215287
Through the question, answers, and comments, I think we established:
_Generic
).typedef
.So the answer seems to be that there's no reliable pre-C11 method, and seemingly no valid pre-C99 method.
Upvotes: 6
Reputation: 2881
Couldn't you stringify the macro and look at the string?
# include <stddef.h>
# include <stdio.h>
# include <string.h>
# define STRINGIFY(x) STRINGIFY_AUX(x)
# define STRINGIFY_AUX(x) #x
int main(void)
{
const char *NULL_MACRO = STRINGIFY(NULL);
if (strstr("void", NULL_MACRO) != NULL)
puts("pointer");
else
puts("integer");
}
It correctly prints "integer"
if you add this (usually NULL
has pinter type):
# undef NULL
# define NULL 0
NULL
cannot be something like (int) ((void *) 0)
because the standards doesn't state that a null pointer constant converted to an integer type is still a null pointer constant.
Furthermore, the standard also say this about integer constant expressions (C11, 6.6/6):
An integer constant expression117) shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants,
sizeof
expressions whose results are integer constants,_Alignof
expressions, and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types, except as part of an operand to thesizeof
or_Alignof
operator.
EDIT: actually this doesn't work with things like:
# define NULL (sizeof(void *) - sizeof(void *))
(thanks for noticing) and this cannot be checked in a trivial way as the OP needs, a little bit of work (simple parsing) is required.
EDIT 2: and there are also typedef
as comment correctly pointed out.
Upvotes: 3
Reputation: 52294
Get the string definition of NULL and then do an as complete check as you want. Here is a very simple minded one:
#define XSTR(x) #x
#define STR(x) XSTR(x)
if (STR(NULL)[0] == '(') {
...
}
But I don't know how you'll handle a __null
which can come out from that.
Upvotes: 4