[comp.lang.c] NULL etc.

snl@cs.cmu.edu (Sean Levy) (06/26/88)

Here's part of what I do:

    #define NIL(t) ((t *)0)     /* might change to some strange */
                                          /* number for the 48-bit datapath
HP's :-) */
    #define NULL NIL(char) /* for backwards compatibility */

Then, f'rinstance

    struct hostent *hp;

    hp = gethostbyname(argv[1]);
    if (hp == NIL(struct hostent)) /* call lost */
     ...

I find this much easier to read. I don't write NULL in code anymore,
I use NIL(char). Being a combo Lisp/C programmer, NIL makes
more sense to me as a name for the pointer that points at nothing
than NULL does. I type fast enough that I don't really make an issue
of having to type an extra 5 characters to get what everyone seems to
call a "NULL pointer" on this list. I would add that distinctions between
0 and a pointer become VERY important at a place like CMU, where
code may be compiled on RT's, Suns, uVaxen and god knows what
else. The above #define's go in a header file called "basic.h" on my
personal include path. I only include <stdio.h> in modules that, well,
do I/O. I'm also going to switch completely to C++ verrrry soon...

  Sean Levy
  Engineering Desingn Research Center (EDRC), CMU
  Internet: snl @ cs.cmu.edu
  BITNET: snl%cs.cmu.edu@cmccvma
  UUCP: beast me. here's a couple that seem to work
     west coast: ...!{ucsdhub|sdcsvax}!snl@cs.cmu.edu
     east cost: ...!harvard!snl@cs.cmu.edu

gwyn@brl-smoke.ARPA (Doug Gwyn ) (06/27/88)

In article <MWlAyzy00hIE82Q1Qc@cs.cmu.edu> snl@cs.cmu.edu (Sean Levy) writes:
>    #define NIL(t) ((t *)0)
>    #define NULL NIL(char)

First of all, NULL must be either "0" or "((void *)0)" (the latter only
if your compiler understands void *s), so your definition is nonportable.

>    struct hostent *hp;
>    hp = gethostbyname(argv[1]);
>    if (hp == NIL(struct hostent))
>     ...

You might as well write that condition as
	if (hp == 0)
which is correct portable C.

>I would add that distinctions between 0 and a pointer become VERY
>important at a place like CMU, where code may be compiled on RT's,
>Suns, uVaxen and god knows what else.

I assume you mean the difference between an INTEGER 0 and a NULL
POINTER of a particular type (null pointers do come in types).
In many cases what looks like an integer 0 in source code actually
does represent a null pointer of the appropriate type, as in my
suggested replacement condition.  The main case where special care
is needed is when passing a pointer argument to a function.  Unless
a prototype is in scope (possible with newer C compilers only), an
argument written as just "0" will be passed as an integer, which is
a type mismatch.  Casting the 0 to the appropriate pointer type is
appropriate in such cases.

Paul_L_Schauble@cup.portal.com (06/29/88)

I don't mean to extend the continuing discussion about the value of NULL, but
i have to ask:

  is #define NULL (char *)0 really portable??

I'm assuming a somewhat odd machine for sake of argument. Consider the
common sequence
1:    long *ptr_to_long;
2:    ptr_to_long = 0;
3:    if (ptr_to_long == NULL) .....

I can visualize a machine where pointer to long (word aligned) and pointer
to char have different formats and for the null pointers in each type to
have different non-zero values. 

Clearly the compiler is obligated to convert to the appropriate null
pointer value in line 2. Expanding the macro and the cast in line 3 this 
becomes

1a:	long *ptr_to_long;
2a:	char *temp;
3a:	ptr_to_long = 0;
4a:	temp = 0;
5b:	if (ptr_to_long == temp) xxxx

Again, the compiler is obligated to convert to the appropriate null value
in 3a and 4a. But not in 5b. So they may well compare unequal. (I think.)

According to K&R, the comparison in 5b is undefined. Pointer comparisons
are only defined between pointer of the same type that point at the same
array. 

This becomes a very interesting question since I have seen postings that
state that the value on NULL for ANSI C is (void *)0. Seems to me that this
has all of the same problems. Is this really what the standard says? If so,
where have I gone wrong above?

    Paul
  implemented, although the space station looks like it's going to be
another bare-bones minimum budget thing with all the emphasis on keeping the
up-front cost low, regardless of operational cost.  Sigh.
     But that's the province of the politicians and beaurocrats.
    It's also hard to believe just how conservative the actual spacecraft
manufacturers are.  Nobody wants to fly anything that hasn't already been
demonstrated in space already.  This is very frustrating to those people who
want to advance the technology--but nobody wants to risk a few million dollars
on something new that might work a tiny bit better if there's any chance at
all that something unfor

gwyn@brl-smoke.ARPA (Doug Gwyn ) (06/30/88)

In article <6966@cup.portal.com> Paul_L_Schauble@cup.portal.com writes:
>  is #define NULL (char *)0 really portable??

No.  It never has been, and in Standard C it will cause diagnostics.

>This becomes a very interesting question since I have seen postings that
>state that the value on NULL for ANSI C is (void *)0. Seems to me that this
>has all of the same problems. Is this really what the standard says? If so,
>where have I gone wrong above?

Where you have gone wrong is in not reading carefully (or not being
sufficiently particular about whom you believe).

Standard-conforming implementations shall define NULL (in the headers
where it is defined) as either 0 or ((void *)0), implementor's choice.
The latter definition can be used to catch more programming errors (for
example, using NULL as a case in a switch or as an array index).

void *s have special properties (not surprising, since there are no void
objects).  The relevant property here is that any valid pointer may be
converted to void * and back without loss of significant information.
This, combined with the requirement that null pointers of all types
compare equal, implies that ((void *)0) is just as good as 0 for use as a
"generic" null pointer.  ("Generic" is in quotes because NULL isn't really
generic, it just acts that way in most contexts.)

jfh@rpp386.UUCP (John F. Haugh II) (07/02/88)

In article <6966@cup.portal.com> Paul_L_Schauble@cup.portal.com writes:
>I don't mean to extend the continuing discussion about the value of NULL, but
>i have to ask:
>
>  is #define NULL (char *)0 really portable??

YES, YES, YES.  by DEFINITION it is portable.  this is why this entire
discussion is so damned pointless.  as Doug Gywn is fond of saying, if
it doesn't work, then you aren't using C.

Chris - here is another topic for your posting.

- john.
-- 
John F. Haugh II                 +--------- Cute Chocolate Quote ---------
HASA, "S" Division               | "USENET should not be confused with
UUCP:   killer!rpp386!jfh        |  something that matters, like CHOCOLATE"
DOMAIN: jfh@rpp386.uucp          |             -- with my apologizes

chris@mimsy.UUCP (Chris Torek) (07/03/88)

>In article <6966@cup.portal.com> Paul_L_Schauble@cup.portal.com asks:
>>is #define NULL (char *)0 really portable??

In article <3458@rpp386.UUCP> jfh@rpp386.UUCP (John F. Haugh II) answers:
>YES, YES, YES.  by DEFINITION it is portable. ...
>Chris - here is another topic for your posting.

I suppose so, because the answer is `no', or at least, not without more
context.

C's untyped nil pointer, which MUST be given a type before it can be
used correctly, is written as `0' (and `0L', and possibly using
constant integer expressions, depending on whose definition you use;
but `0' suffices and must work).

After it has been given a type (`(char *)0') it becomes a nil pointer
of that type.  Once it has a type (if we ignore some fine points in the
dpANS, many of which are unlikely to be implemented in current C
compilers) it may not be used as a nil pointer of another type.  Hence
(char *)0 is a nil pointer to char, and as such may not be used as a
nil pointer to int, or a nil pointer to struct tcurts, or indeed as
anything other than a pointer to char.  It may work---indeed, it is
more likely to work than to fail---but it is incorrect and unportable,
and should (and does in PCC) draw at least a warning from the compiler.

There are only two ways that the untyped nil pointer can acquire a
type, namely assignment and comparison.  Casts are a special case of
assignment, as are arguments to functions that have prototypes in
scope.  Where this causes the most trouble is in arguments to functions
that do not have prototypes in scope, or for which the prototype does
not specify a type for that argument: e.g., execl():

	f() {
		void execl(char *, ...);

		execl("prog", "prog", "arg1", "arg2", ___);
	}

The only correct way to fill in the blank is with (char *)0 (or
possibly (char *)0L and similar tricks; outside of obfuscated C
contests, these tricks are not worth considering).

The dpANS has at present one more instance of an `untyped' nil pointer,
namely `(void *)0'.  This is an anomaly in the type system, and, while
it has some beneficial properties, I believe that overall it makes the
situation worse, not better.  The differences between using `0' and
`(void *)0' as a `generic nil' are, first, that while 0 is also an
integer constant, (void *)0 is not, and second, that (void *)0 is also
a typed nil pointer (ouch!---more below).

Suppose that NULL is defined as either `0' or `(void *)0'---one of the
two untyped nil pointers---but that we do not know which one.  Which of
the following calls are correct?

	/* defintions before the fragments (note lack of prototypes) */
	void f1(cp) char *cp; { <code> }
	void f2(ip) int *ip; { <code> }
	void f3(vp) void *vp; { <code> }

	...
		f1(NULL);		/* call 1 */
		f1((char *)NULL);	/* call 2 */
		f2(NULL);		/* call 3 */
		f2((int *)NULL);	/* call 4 */
		f3(NULL);		/* call 5 */
		f3((void *)NULL);	/* call 6 */

It is easy to see that calls 2, 4, and 6 (which cast their arguments
and hence provide types) are correct.  The surprise is that while calls
1, 3, and 5 are all wrong if NULL is defined as `0', calls 1 and 5 are
both correct, or at least will both work, if NULL is defined as
`(void *)0'.  Call 3 is wrong in any case.

We can get away with `f1((void *)0)' only because of a technicality:
the dpANS says that (void *) and (char *) must have the same
representation (which more or less means `must be the same type'), and
because (void *) is a valid pointer type, (void *)0 must be a valid nil
pointer of type `void *', and thus must also be a valid nil pointer of
type `char *'.  (Actually, this argument glosses over a subsidiary
technicality, in that there is no guarantee that there is only ONE
valid nil pointer of any given type, but that way lies madness.  There
are more arguments about whether `same representation' implies
`indistinguishable'; these, too, are best left untouched.)

There are no ANSI-conformant C compilers, for there is as yet no ANSI C
standard.  One should therefore assume that code may have to run under
a compiler where NULL is defined as `0', not as `(void *)0', and should
therefore avoid calls like 1, 3, and 5 above.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

maart@cs.vu.nl (Maarten Litmaath) (07/05/88)

In article <12290@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
\		void execl(char *, ...);

Doesn't execl() return int anymore? With the cause of the failure in errno?
-- 
I'd rather live in Russia             |Maarten Litmaath @ Free U Amsterdam:
              than in South-Africa... |maart@cs.vu.nl, mcvax!botter!ark!maart

chris@mimsy.UUCP (Chris Torek) (07/06/88)

>In article <12290@mimsy.UUCP> I wrote
>>		void execl(char *, ...);

In article <1315@ark.cs.vu.nl> maart@cs.vu.nl (Maarten Litmaath) asks:
>Doesn't execl() return int anymore? With the cause of the failure in errno?

Well, actually, yes; but it only ever returns -1, so declaring it
as `int' is somewhat pointless---the value is even less useful than
that from, e.g., strcpy().
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

nevin1@ihlpf.ATT.COM (00704a-Liber) (07/06/88)

In article <1315@ark.cs.vu.nl> maart@cs.vu.nl (Maarten Litmaath) writes:
>In article <12290@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>\		void execl(char *, ...);

>Doesn't execl() return int anymore? With the cause of the failure in errno?

It still does (at least according to my System V manual); as a matter of
fact, the ONLY value it can return is -1.

More to the point:  why would you want to check the return value of
exec()?  The only way it can return to the calling process is if an error
occurs, so why check the return value?  It seems like a waste of code.
-- 
 _ __			NEVIN J. LIBER	..!ihnp4!ihlpf!nevin1	(312) 510-6194
' )  )				You are in a twisty maze of little
 /  / _ , __o  ____		 email paths, all different.
/  (_</_\/ <__/ / <_	These are solely MY opinions, not AT&T's, blah blah blah