Definition of NULL

J

Jason Curl

Jason said:
From what I gather reading the C FAQ is the best way is to typecast
NULL to (char*) as it could be such a machine where an (int*) has a
different representation to (char*). The compiler wouldn't know what to
cast a (void*) to. I guess it works because on most machines (void*) has
the same representation internally as (int*) and (char*).

It appears from everything I've read so far that just using NULL is not
portable, as it was my original intention of a slight optimisation of
using calloc instead of malloc and setting the pointers to NULL explicitly.

Sorry - Clarification - just using NULL in this context for variadic
functions is not portable. It's also not portable to assume that an
internal representation for a null pointer is zero.

Sorry I didn't read my response the first time!
 
O

Old Wolf

Michael Mair said:
Is it clear to you if you think of 0 instead of (void *) 0?
If you give a prototype of that function, the compiler _knows_
the respective parameter type. Casting it is only an optical
help for you.

The compiler does not know the parameter type if the function
is variadic. For variadic functions you must cast the parameters
if their type is not what the function is expecting.

The OP did not bother to say what the prototype
for "execl" was. If we suppose for the moment that he meant:

int execl( const char *path, const char *arg, ...);

and that the function expects the extra arguments to be
of type "char *", then it would be undefined behaviour
to pass a bare NULL like he did in the first example.
 
M

Michael Mair

Old said:
The compiler does not know the parameter type if the function
is variadic. For variadic functions you must cast the parameters
if their type is not what the function is expecting.

The OP did not bother to say what the prototype
for "execl" was. If we suppose for the moment that he meant:

int execl( const char *path, const char *arg, ...);

and that the function expects the extra arguments to be
of type "char *", then it would be undefined behaviour
to pass a bare NULL like he did in the first example.

Thanks for pointing that out.
I gathered from the rest of the discussion that execl() is supposed
to be variadic, too. This was completely unclear to me(*) which is
why I only considered the case that there was no prototype as
possible special case where I was not sure. You snipped that part
but essentially I would just use the cast if I had only a declaration
like
int execl();
but were sure about the number and types of arguments.


Cheers
Michael
____
(*) And that is a good example why people should at least have
the decency to provide prototypes for nonstandard functions they
are using.
 
P

pete

Jason said:
I guess it works because on most machines (void*) has
the same representation internally as (int*) and (char*).

The representation of void* and char* is the same.

N869
6.2.5 Types
[#27] A pointer to void shall have the same representation
and alignment requirements as a pointer to a character type.
 
J

Jason Curl

Michael said:
Thanks for pointing that out.
I gathered from the rest of the discussion that execl() is supposed
to be variadic, too. This was completely unclear to me(*) which is
why I only considered the case that there was no prototype as
possible special case where I was not sure. You snipped that part
but essentially I would just use the cast if I had only a declaration
like
int execl();
but were sure about the number and types of arguments.


Cheers
Michael
____
(*) And that is a good example why people should at least have
the decency to provide prototypes for nonstandard functions they
are using.

Sorry if it wasn't clear about the prototype. But I used this because
the C FAQ used this, and I was asking for clarification on my second
post about the C FAQ. I had really thought that the original post made
it clear I was talking about an example in the C FAQ:

"execl("/bin/sh", "sh", "-c", "date", (char *)NULL);
or is this wrong too, and it should be as in Q5.2?"
 
C

Charlie Gordon

Jason Curl said:
context.

From what I gather reading the C FAQ is the best way is to typecast
NULL to (char*) as it could be such a machine where an (int*) has a
different representation to (char*). The compiler wouldn't know what to
cast a (void*) to. I guess it works because on most machines (void*) has
the same representation internally as (int*) and (char*).

I Agree. But in this case, this is not the issue as char* and void* are supposed
to have the same representation.
The problem is with environments that define NULL this way :
#define NULL 0

This is needed for C++ because of their dubious choice to disallow implicit cast
of void* to other pointer types.

For C, it is not needed. It is correct but IMHO misleading :

It doesn't allow the compiler to catch blatant misunderstandings :

char buf[10];
buf[0] = NULL;

time_t tv = NULL;

double epsilon = NULL;

All these would get caught if NULL was (void*)0.
They will on a different architecture, so that ugly code is non portable anyway.

As per my initial example, having to cast NULL to whatever pointer type is
passed to a varadic function is needed, but a common mistake. And in the
following example :

printf("%p", NULL);

having to cast NULL to (void*) would definitely seem overkill, even though it
would be required to prevent bogus output on a the 64 bit ABI detailed above.

Conclusion : it is safer to define NULL to ((void*)0)

Chqrlie.
 
J

Jason Curl

pete said:
Jason Curl wrote:

I guess it works because on most machines (void*) has
the same representation internally as (int*) and (char*).


The representation of void* and char* is the same.

N869
6.2.5 Types
[#27] A pointer to void shall have the same representation
and alignment requirements as a pointer to a character type.
Thanks Pete. This opens up a new interesting set of conundrums for myself.

Let's imagine we have a function signature:

void CallBack(void *data);

I use often in code this type of datastructure, where I might say:

typedef void (cb)(void *data);

int RegisterCallBack(cb *fn, void *data);

int result;
struct _myData {
int foo;
char bar;
} myData;

int main(void) {
result = RegisterCallBack(CallBack, &myData);

return MainEventLoop();
}

So that, when I call a function in a loop that could for example monitor
external inputs (keyboard, a hardware switch, it doesn't matter), when a
condition is met my function 'CallBack()' is called. CallBack() would of
course typecast (_myData*)data.

How does the standard deal with the case when sizeof(_myData *) is
smaller than sizeof(void*) (or even indeed if the internal
representations might be different)?

I can only imagine that void* is 'typeless' so it is automatically
promoted to _myData* with any conversions required. Indeed, it might be
an int* instead of _myData*.

This then implies to me that void* must be able to accomodate all types
of pointers
 
L

Lawrence Kirby

On Thu, 18 Nov 2004 15:57:23 +0100, Jason Curl wrote:

....
Let's imagine we have a function signature:

void CallBack(void *data);

I use often in code this type of datastructure, where I might say:

typedef void (cb)(void *data);

int RegisterCallBack(cb *fn, void *data);

int result;
struct _myData {

Avoid defining identifiers starting with underscores. There are
situation where this is OK, but it is FAR easier not to have to worry
about it than to remember all the rules.
int foo;
char bar;
} myData;

int main(void) {
result = RegisterCallBack(CallBack, &myData);

return MainEventLoop();
}

So that, when I call a function in a loop that could for example monitor
external inputs (keyboard, a hardware switch, it doesn't matter), when a
condition is met my function 'CallBack()' is called. CallBack() would of
course typecast (_myData*)data.

Since C will convert implicitly from void * to other
non-function pointer types, a cast is not explicitly required and should
probably be avoided.
How does the standard deal with the case when sizeof(_myData *) is
smaller than sizeof(void*) (or even indeed if the internal
representations might be different)?

C says you can convert from a valid pointer to a data type to void * and
back again and you will get the original value back. Aldo the void * value
will be a pointer to the first byte of the object pointed to, or null if
the original pointer was null.

So if the 2 pointer types have different representations the compiler must
make the appropriate representation changes. This is perfectly reasonable
and normal concept when converting a value to a different type in C.
I can only imagine that void* is 'typeless'

The pointer has a perfectly well-defined type and representation (as much
as the representation of any pointer is defined).

so it is automatically
promoted to _myData* with any conversions required. Indeed, it might be
an int* instead of _myData*.

Pointers aren't automatically (*) "promoted", they are converted from one
type to another as the source code requires.

(*) there are some circumstances where you could argue this e.g.

int *ip = ...;
void *vp = ...;

ip == vp /* the value of ip is converted to void * and compared to vp */

cond ? ip : vp; /* the result is either ip converted to void * or vp */

The conversion has to be this way because every valid int * can be
converted to a void * but the reverse is not guaranteed.
This then implies to me that void* must be able to accomodate all types
of pointers

In the sense of being able to convert to void * and back again, yes,
except for pointers to functions for which there are no such guarantees.

Lawrence
 
K

Keith Thompson

pete said:
The representation of void* and char* is the same.

Yes, but that doesn't help in the example we're discussing. The
execl() function (which is defined by POSIX but not by the C standard)
takes a variable number of arguments. If you pass NULL as one of the
variable arguments, the compiler has no way to know what type the
function is expecting (i.e., how the function is going to call
va_arg()). As it happens, the function is expecting the argument to
be of type char*. If NULL is defined as (void*)0, you're probably ok,
since void* and char* have the same representation. But if NULL is
defined as 0 (which is an equally valid definition), the argument is
going to be passed as an int.

Many systems will fail to catch this error, either because NULL is
defined as (void*)0, or because int and char* happen to be the same
size *and* a null char* is represented as all-bits-zero *and* variable
arguments of types int and char* are passed by the same mechanism.
Break any one of these last three assumptions, and it's likely to blow
up in your face.

Of course, it's much easier and safer to add the cast than to figure
out whether you can get away with omitting it.
 
P

Peter Shaggy Haywood

Groovy hepcat Jason Curl was jivin' on Tue, 16 Nov 2004 18:47:59 +0100
in comp.lang.c.
Definition of NULL's a cool scene! Dig it!
I've been reading this newsgroup for some time and now I am thoroughly
confused over what NULL means.

I've read a NULL pointer is zero (or zero typecast as a void pointer),
others say it's compiler dependent (and that NULL might be anything, but
it is always NULL).

It's really very simple. You're probably confusing three things,
which is a very common error.

1) A null pointer is a pointer guaranteed not to point at an object or
function. The actual representation of a null pointer is not
specified.

2) A null pointer constant is a constant of type int with a value of 0
or such a constant cast to void *. In a pointer context it is
converted to a null pointer.

3) NULL is a macro (defined in various standard C headers) that
evaluates to a null pointer constant.
The source snippet is below. The question is:
- When I use calloc to allocate a block of memory, preinitialising it to
zero, is this equivalent (and portable C) to iterating through the
structure 128 times setting the individual elements to NULL (i.e. is
myFunc() and myFunc2() equivalent in functionality)?

No, not quite. The problem is that calloc() sets the memory it
allocates to "all bits zero". But a null pointer is not necesssarily
"all bits zero". Assigning a null pointer constant (which has the
value 0) to a pointer is guaranteed to make it a null pointer; but
this does not necessarily mean "all bits zero". As stated in point 1)
above, the representation is not specified.
struct _KeySequence {
KeyCallback *cb;
struct _KeySequence *next;
};

struct _KeySequence *node;

int myFunc(void)
{
node = calloc(128, sizeof(struct _KeySequence));
if (node == NULL) {
return 0;
} else {
return 1;
}
}

int myFunc2(void)
{
int j;

node = malloc(128 * sizeof(struct _KeySequence));
if (node == NULL) {
return 0;
} else {
for (j=0; j<127; j++) {
node[j].cb = NULL;
node[j].next = NULL;
}
return 1;
}
}

myFunc2() sets all the pointers in the array to null pointers.
myFunc(), however, may or may not, depending on how null pointers are
represented in the implementation. Technically the value of these
pointers is, therefore, indeterminate.

--

Dig the even newer still, yet more improved, sig!

http://alphalink.com.au/~phaywood/
"Ain't I'm a dog?" - Ronny Self, Ain't I'm a Dog, written by G. Sherry & W. Walker.
I know it's not "technically correct" English; but since when was rock & roll "technically correct"?
 

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
474,154
Messages
2,570,870
Members
47,400
Latest member
FloridaFvt

Latest Threads

Top