[comp.lang.c] Header problems

decot@hpisod2.HP.COM (Dave Decot) (03/03/88)

If I am reading from an old draft, or am misunderstanding something,
please feel free to (gently!) correct me.

The requirement that headers described by the ANSI-C draft shall
not define any "stray" (implementation-specific or internal) types or
constants, and the requirement that inclusion of only one header
shall be required for the use of any ANSI-C library function,
should be dropped.

While trying to avoid "name space pollution" is admirable,
these requirements require implementations to break thousands of existing
applications, or to define all constants in all headers.

There is no place that I can define existing non-ANSI defined "extensions",
since constants for them cannot be placed in the appropriate header,
and I am not allowed to require applications to include another.

I don't know in which header to define "NULL", since it is
required by facilities that also require other constants,
and I am not allowed to add these other constants to <stddef.h>.

Dave Decot
hpda!decot

gwyn@brl-smoke.ARPA (Doug Gwyn ) (03/04/88)

In article <2550049@hpisod2.HP.COM> decot@hpisod2.HP.COM (Dave Decot) writes:
>While trying to avoid "name space pollution" is admirable,
>these requirements require implementations to break thousands of existing
>applications, or to define all constants in all headers.

I don't understand your argument.  It is true that the incredibly sloppy
UNIX headers need to be cleaned up for ANSI C conformance.  Based on the
amount of trouble I've had porting applications due to name space pollution,
I think this is a Good Thing.  Unlike IEEE 1003.1, X3J11 is not nearly so
concerned about allowing vendors to rubber-stamp "standard conforming" on
whatever crufty implementations they may have already produced.  A
conforming ANSI C implementation will be considerably "cleaner" than is
usually the case in existing practice.  This was specified deliberately in
order to actually promote application program portability among future
standard-conforming implementations, rather than pay this all-important
goal lip service while not resolving the most important portability issues.

The current draft specifies the headers in which standard library functions
are declared and those in which standard "manifest constant" macros are
defined.  With two exceptions (NULL and size_t), each such symbol is found
in precisely one header.  (Earlier drafts had this messed up.)  size_t is
defined in each header for which there is a reference to it in the
corresponding section of the standard.

>I don't know in which header to define "NULL", since it is
>required by facilities that also require other constants,
>and I am not allowed to add these other constants to <stddef.h>.

NULL is defined in <stdio.h> (for historical compatibility) and in
<stddef.h> (where it really belongs).  If you need to use the symbol,
you should include one or both of these two headers.  Of course NULL
is only a "convenience" macro, because it is clear how to write any
type of null pointer constant in the language proper.  It was included
in the propsoed standard because so much code uses it, not because it
is technically necessary for portability.

The implementor should put something like the following in each header:
	#define NULL 0


P.S. These are my view, not necessarily X3J11's.

dag@chinet.UUCP (Daniel A. Glasser) (03/08/88)

In article <7412@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
>NULL is defined in <stdio.h> (for historical compatibility) and in
><stddef.h> (where it really belongs).  If you need to use the symbol,
>you should include one or both of these two headers.  Of course NULL
>is only a "convenience" macro, because it is clear how to write any
>type of null pointer constant in the language proper.  It was included
>in the propsoed standard because so much code uses it, not because it
>is technically necessary for portability.
>
>The implementor should put something like the following in each header:
>	#define NULL 0
>
>
>P.S. These are my view, not necessarily X3J11's.


Your use of NULL === 0 promotes unportable code, and is considered to be
bad programming style in a world where portability across multiple machine
architectures (ie, PDP-11, VAX-11, M680x0, I80x86 and Z800x) is required
for commercial reasons.

On machines were sizeof int != sizeof(void *), the above definition will
not work on older style function calls (without prototypes) or in var-arg
situations.  Requiring sizeof int == sizeof(void *) is not a viable
solution.  A better way to do it, in each place you want to define NULL,
is:

	#ifndef	NULL
	#define	NULL	((void *)0)
	#endif

On older compilers, replace 'void' with 'char'.  This allows the use of
a short NULL representation when the compiler is smart enough to use it
but forces passing of NULL in argument lists as a pointer sized object.

What the C language guarentees is not that 0===NULL, but that a constant 0
can be assigned/compared to a pointer without warning or error regardless
of pointer representation and compare as equal to the NULL pointer.
In cases where the compiler is unable to determine if an int or type *
is being passed, 0 is passed as an int.  The use of the name NULL in the
preprocessor does not affect this.

('===', as used above, means exactly equivilant)
-- 
			   Daniel A. Glasser
...!ihnp4!chinet!dag || ...!ihnp4!mwc!dag || ...!ihnp4!mwc!gorgon!dag
    One of those things that goes "BUMP!!! (ouch!)" in the night.

g-rh@cca.CCA.COM (Richard Harter) (03/09/88)

In article <3351@chinet.UUCP> dag@chinet.UUCP (Daniel A. Glasser) writes:
>In article <7412@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
>
>Your use of NULL === 0 promotes unportable code, and is considered to be
>bad programming style in a world where portability across multiple machine
>architectures (ie, PDP-11, VAX-11, M680x0, I80x86 and Z800x) is required
>for commercial reasons.
>
	This comes up regularly.  There is nothing wrong defining NULL as
0; it does not make for non-portability.  The fault lies in passing NULL
as an uncasted pointer across a calling sequence.  Since lint will tell
you about this, there is no good reason for this happening.  In actual
fact, I am wary of using (void *)0 for portability reasons -- I don't
know (and don't care) if all implementations that I have to deal with
handle it correctly.  I do know that all implementations handle
(proper_type *)0 correctly.  [Actually I don't know that, but I've never
seen it fail.]
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

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

>In article <7412@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn) writes:
>>The implementor should put something like the following in each header:
>>	#define NULL 0

0) right

In article <3351@chinet.UUCP> dag@chinet.UUCP (Daniel A. Glasser) writes:
>Your use of NULL === 0 promotes unportable code,

1) wrong

>On machines were sizeof int != sizeof(void *), the above definition will
>not work on older style function calls (without prototypes) or in var-arg
>situations.

2) poorly stated.  Incorrect code, such as the following:

	main { f(NULL); exit(0); }		/* wrong */
	f(s) char *s; { if (s != NULL) printf("%s\n", s); }

will indeed fail on such machines.  Correct code, acheived by changing
the first line to

	main { f((char *)NULL); exit(0); }	/* right */

will work properly with `#define NULL 0'.  Similar incorrect code will,
on some machines, fail no matter what the definition of NULL.  Leaving
out the prototype (where available) or the cast is an error; making it
appear to work is, I claim, a disservice.

>A better way to do it, in each place you want to define NULL, is:
> 	#ifndef	NULL
> 	#define	NULL	((void *)0)
> 	#endif

The dpANS legislates that this must work, again with the proviso that
the source code be correct (prototypes and/or casts present
everywhere).  The type (void *) does not exist in many pre-dpANS
compilers, hence I claim this definition is not as good.  All it does
is hide the old Vax-era errors on some other machines.  It does not
fix them.

>What the C language guarentees is not that 0===NULL, but that a constant 0
>can be assigned/compared to a pointer without warning or error regardless
>of pointer representation and compare as equal to the NULL pointer.

Correct (although I think it is better stated as `... compare as equal
to a nil pointer of the type of the first pointer').

>In cases where the compiler is unable to determine if an int or type *
>is being passed, 0 is passed as an int.  The use of the name NULL in the
>preprocessor does not affect this.

Also correct.  The solution, however, is to provide the context via
casts and/or prototypes, not to use (void *), which (if it exists at
all) may not have the same representation as, e.g., (int *) (and in
fact does not on several existing machines).  The following code is
incorrect:

	#undef NULL
	#define NULL ((void *)0)
	main() { f(NULL); exit(0); }		/* wrong */
	f(p) int *p; { if (p != NULL) *p = 11; }

(whether it appears to work on your machine is irrelevant).  This
version is correct:

	#undef NULL
	#define NULL 0
	main() { f((int *)NULL); exit(0); }	/* right */
	f(p) int *p; { if (p != NULL) *p = 11; }

Finally, note that in this last example, NO SINGLE DEFINITION OF NULL
can make the two lines marked `wrong' correct:

	main() {
		f1(NULL);	/* wrong */
		f2(NULL);	/* wrong */
		exit(0);
	}

	f1(cp) char *cp; { if (cp != NULL) *cp = 'a'; }
	f2(dp) double *dp; { if (dp != NULL) *dp = 2.2; }

The only way to fix this last example is by adding prototypes and/or
casts.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

shirono@grasp.cis.upenn.edu (Roberto Shironoshita) (03/09/88)

In article <3351@chinet.UUCP> dag@chinet.UUCP (Daniel A. Glasser) writes:
>In article <7412@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
>>The implementor should put something like the following in each header:
>>	#define NULL 0
>
>Your use of NULL === 0 promotes unportable code, and is considered to be
>bad programming style in a world where portability across multiple machine
>architectures (ie, PDP-11, VAX-11, M680x0, I80x86 and Z800x) is required
>for commercial reasons.

K&R says, on page 97 (I think it is the 22nd printing of the 1978
edition),

	"C guarantees that no pointer that validly points at data will
	 contain zero ..."

Since from what Doug says NULL remains in the language because of its
widespread use in K&R-conforming programs, then his #define is valid.

>On machines were sizeof int != sizeof(void *), the above definition will
>not work on older style function calls (without prototypes) or in var-arg
>situations.  Requiring sizeof int == sizeof(void *) is not a viable
>solution.  A better way to do it, in each place you want to define NULL,
>is:
>
>	#ifndef	NULL
>	#define	NULL	((void *)0)
>	#endif

First I don't see where Doug requires that sizeof int == sizeof(void *).
Second, your #define might break a lot of good code, which is what
X3J11 tries to avoid by leaving NULL (as defined in K&R) in the
standard.

>What the C language guarentees is not that 0===NULL, but that a constant 0
>can be assigned/compared to a pointer without warning or error regardless
>of pointer representation and compare as equal to the NULL pointer.

You seem to forget that what the preprocessor does is to replace every
occurrence of NULL (except inside strings and character literals) with
whatever it was #define'd.  So, the lexical analyzer doesn't see the
lexeme `NULL'; it sees the constant 0.  Hence, to the programmer, if
there is a `#define NULL 0' anywhere in his code, NULL === 0.  Your
point is valid, in that the above doesn't necessarily mean that
NULL === (void *)0

>In cases where the compiler is unable to determine if an int or type *
>is being passed, 0 is passed as an int.  The use of the name NULL in the
>preprocessor does not affect this.

True.  Which is why a portability-conscious programmer should use
explicit type casts in his function calls.

                                  Roberto Shironoshita
-------------------------------------------------------------------------
1- The University doesn't know I exist.   | Internet:
2- Of course I may be wrong.              |   shirono@grasp.cis.upenn.edu

flaps@dgp.toronto.edu (Alan J Rosenthal) (03/09/88)

Doug Gwyn <gwyn@brl.arpa> wrote about NULL, and said that the proper
definition of it is ``#define NULL 0''.

dag@chinet.UUCP (Daniel A. Glasser) wrote:
>Your use of NULL === 0 promotes unportable code ... [moralizing deleted]
>
>On machines were sizeof int != sizeof(void *), the above definition will
>not work on older style function calls (without prototypes) or in var-arg
>situations.  Requiring sizeof int == sizeof(void *) is not a viable
>solution...
>
>	#define	NULL	((void *)0)
>On older compilers, replace 'void' with 'char'.  This allows the use of
>a short NULL representation when the compiler is smart enough to use it
>but forces passing of NULL in argument lists as a pointer sized object.

This doesn't require sizeof(int) == sizeof(void *).  The assumption is
that you always cast NULL before passing it.  Casting is still required
if NULL is defined as ((void *)0); you can't assume that all pointers
have the same size or representation.  Passing a pointer to void to a
routine that expects a pointer to struct gosh is as big a mistake as
passing an int to a routine that expects a pointer to struct gosh.

By the way, if you call f(0) where f expects a char *, the requirement
for it to work is not simply that sizeof(int) == sizeof(char *), but
also that (int)0 and (char *)0 have the same bit representation.

With respect to your phrase "a pointer sized object" - there is no such
thing in C.  There may happen to be such a thing in a particular
implementation of C due to hardware considerations.  (Obviously, it is
usually advantageous to represent all pointers in the same way (but not
always); therefore this is a common occurrence.)

ajr
-- 
If you had eternal life, would you be able to say all the integers?

jdc@naucse.UUCP (John Campbell) (03/09/88)

In article <25398@cca.CCA.COM>, g-rh@cca.CCA.COM (Richard Harter) writes:
> >
> 	This comes up regularly.  There is nothing wrong defining NULL as
> 0; it does not make for non-portability.  The fault lies in passing NULL
> as an uncasted pointer across a calling sequence.  Since lint will tell
> you about this, there is no good reason for this happening.

I've enjoyed and appreciated Richard's contributions to the net these
past weeks, but he should realize that not all 'C' code is written on
unix.  I am (gasp) developing under VMS.  I don't have lint; VMS compiler
options are suppose to be all I need :-(.

A while back a program called check(1) was posted.  I could not, easily,
get this to work on VMS.  I looked at a local copy of the pre-processor
for gnu-c and decided I didn't want to try to get that working on VMS.
I should probably abandon VMS :-) but I get paid to work on it  
(paid to create code that might be non-portable due to not having a lint).

PS anyone know of VMS lints for free?
PPS who's writing the next ANSI lint?
PPPS Thanks, again, to Chris Torek for NULL sanity.
-- 
	John Campbell               ...!arizona!naucse!jdc

	unix?  Sure send me a dozen, all different colors.

g-rh@cca.CCA.COM (Richard Harter) (03/10/88)

In article <604@naucse.UUCP> jdc@naucse.UUCP (John Campbell) writes:
>In article <25398@cca.CCA.COM>, g-rh@cca.CCA.COM (Richard Harter) writes:

>I've enjoyed and appreciated Richard's contributions to the net these
>past weeks, but he should realize that not all 'C' code is written on
>unix.  I am (gasp) developing under VMS.  I don't have lint; VMS compiler
>options are suppose to be all I need :-(.

This man is clearly one of the most astute and wisest contributors to
this group :-).  He makes a very good point, which I forgot.  We develop
on UNIX and port to VMS -- so we get to do all our lint checking before
we go into alien environemnts.  The great merit of Lint (versus thorough
compiler diagnostics) is that it cross checks function argument and return
value types.
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

gwyn@brl-smoke.ARPA (Doug Gwyn ) (03/10/88)

In article <3351@chinet.UUCP> dag@chinet.UUCP (Daniel A. Glasser) writes:
>In article <7412@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
>>	#define NULL 0
>Your use of NULL === 0 promotes unportable code, and is considered to be
>bad programming style in a world where portability across multiple machine
>architectures (ie, PDP-11, VAX-11, M680x0, I80x86 and Z800x) is required
>for commercial reasons.
>	#define	NULL	((void *)0)

My original advice was precisely correct.  Yours perpetuates the myth
that all pointers have the same representation.  Since this is not so
in general, there is no alternative for the portable C programmer but
to explicitly cast 0 or NULL to the proper type when it is used as a
function parameter.

For ANSI C, your suggestion is as good as mine (but no better); in
non-ANSI C, there is no (void *).  ANSI C has the additional advantage
that either form of NULL used as a function parameter will be
automatically converted to the right type, if a non-variadic function
prototype is in scope.

It seems that we have to go through this discussion every six months.

pablo@polygen.uucp (Pablo Halpern) (03/10/88)

From article <3351@chinet.UUCP>, by dag@chinet.UUCP (Daniel A. Glasser):
>            A better way to do it, in each place you want to define NULL,
> is:
> 
> 	#ifndef	NULL
> 	#define	NULL	((void *)0)
> 	#endif
> 
> On older compilers, replace 'void' with 'char'.  This allows the use of
> a short NULL representation when the compiler is smart enough to use it
> but forces passing of NULL in argument lists as a pointer sized object.

Do NOT replace 'void' with 'char' for old compilers unless you are prepared
to cast every use of NULL to the pointer type you are assigning (or comparing,
etc.).  Most compilers will complain of a pointer type missmatch since
(char *) doesn't match (int *) or (struct foo *) etc..  I agree that for
ANSI compilers, the above is correct, since (void *) DOES match (int *)
by definition.  For compilers that don't support (void *), you must have
a compiler-specific definition of NULL:

	#define NULL 0		/* If sizeof(int) == sizeof(char *) */

or

	#define NULL 0L		/* if sizeof(long) == sizeof(char *) */

This avoids pointer type missmatches because all compilers let you compare
any pointer agains integer zero.  Since the above #defines
are necessarily machine (and compiler) specific, I put these
types of declarations in a file called "machine.h" which contains machine
and compiler-dependent declarations and is different for each compiler I
use.  "machine.h" is #include'd in all of my source files.

Pablo Halpern		mit-eddie \
Polygen Corp.		princeton  \ polygen!pablo
200 Fifth Ave.		bu-cs	   /
Waltham, MA  02254	stellar   /

pedersen@acf3.NYU.EDU (paul pedersen) (03/10/88)

In article <10574@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>Finally, note that in this last example, NO SINGLE DEFINITION OF NULL
>can make the two lines marked `wrong' correct:
>
>	main() {
>		f1(NULL);	/* wrong */
>		f2(NULL);	/* wrong */
>		exit(0);
>	}
>
>	f1(cp) char *cp; { if (cp != NULL) *cp = 'a'; }
>	f2(dp) double *dp; { if (dp != NULL) *dp = 2.2; }
>
>The only way to fix this last example is by adding prototypes and/or
>casts.

I have settled on the following approach, abandoning NULL altogether:
# define NIL(TYPE) ((TYPE *)0)
and now refer to all null pointers as NIL(char), NIL(int), NIL(struct foo), etc.
Similarly, I use the following macros for allocating/freeing:
char *malloc();
char *free();
# define ALLOC(TYPE) ((TYPE *) malloc(sizeof(TYPE)))
# define FREE(p) (free((char *) p))

This syntax is pre-ANSI, but I don't see why it shouldn't work even post-ANSI,
provided the declarations are changed to proper prototypes,
and (void *) is introduced as needed.

I have come to feel that the presence of an explicit cast in the code is
bletcherous, similar to the presence of an explicit manifest constant.

(For people who need to allocate vectors, a CALLOC macro with args TYPE and
n is also probably in order; I haven't had a use for this yet.)

dag@chinet.UUCP (Daniel A. Glasser) (03/10/88)

Okay, I've now been flamed and roasted via mail, news and even on the
phone for my posting about NULL and 0.

If it were not for noisy and slow connections between the system I
do my posting from and where I actually am, I would have written
that to be the safest, you must cast NULL whenever you use it in a
call.  It was my recollection, and I did not have my K&R handy to
check it out when I posted my message, that the C specification in
K&R stated that when pointers to different objects had different
representations, all pointers were passed in the "universal" pointer
representation, just as char is promoted to int, float to double,
so would (foo *) be promoted to the equivilent of (void *), and then
moved back into a (foo *) representation when used at the other end.
Having delt with compilers that did this, I assumed that the language
had this orthoginality.  I'm sorry, I aplogise to everyone who's
life I have ruined with my posting.


I made my posting not out of a lust for power, but because in my
everyday work, I get about 3 calls a week passed to me from the
tech. support people where I work from customers who are porting
VAX unix code to the Atari ST using our compiler where ints are
16 bits, pointers 32 bits, and they often INSIST that K&R allows them
to use 0 or NULL without casts in function calls expecting pointers.
They get upset when I tell them that that is not the case.

If it makes you feel any better, I'll retract what I said.
However, the

	#ifndef ... #endif

or the

	#undef

should be used, and the EXPLICIT casting of any use of NULL in a parameter
list should be stressed.

<flame on>

Please note that of over 100 programs that I have gathered from the
network source groups, not one of them that I have actually looked at
has cast NULL to any kind of pointer when using it in calls.  Not one!
Not even the GNU stuff that I've looked at.

In other words, though proper usage uses casts on NULL, common usage
does not.

<flame off>
-- 
		Daniel A. Glasser	dag@chinet.UUCP
    One of those things that goes "BUMP!!! (ouch!)" in the night.
 ...!att-ih!chinet!dag | ...!ihnp4!mwc!dag | ...!ihnp4!mwc!gorgon!dag

msb@sq.uucp (Mark Brader) (03/10/88)

> By the way, if you call f(0) where f expects a char *, the requirement
> for it to work is not simply that sizeof(int) == sizeof(char *), but
> also that (int)0 and (char *)0 have the same bit representation.

*And* that the implementation passes integer and pointer type values to
functions in the same manner.  Remember, arguments can be passed in registers
and machines can have several types of registers.

*Never* say f(0) or f(NULL).

Mark Brader, SoftQuad Inc., Toronto, utzoo!sq!msb, msb@sq.com
#define	MSB(type)	(~(((unsigned type)-1)>>1))

mls@whutt.UUCP (SIEMON) (03/11/88)

In article <3416@chinet.UUCP>, dag@chinet.UUCP (Daniel A. Glasser) writes:
> 
> ...
> In other words, though proper usage uses casts on NULL, common usage
> does not.
> 
> 		Daniel A. Glasser	dag@chinet.UUCP

Yes; you should realize that you were flamed so much because people were
feeling deeply guilty about their own failure to practice what they preach.
Here was a perfect scapegoat -- someone who could be read as not understanding
what to do and could be lectured about it by those who REALLY intend to do it
right, but somehow don't quite get around to it.  (Please no flames from those
who really do go to the effort to do it right -- you are a rare breed; for
most of us, it is just too damned easy to "code" and too damned hard to think
and to rewrite.

Michael Siemon
contracted to AT&T Bell Laboratories
(these are my opinions, not theirs)
ihnp4!mhuxu!mls

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

[This makes three.]
In article <121@polygen.UUCP> pablo@polygen.uucp (Pablo Halpern) writes:
>For compilers that don't support (void *), you must have
>a compiler-specific definition of NULL:

No.

>	#define NULL 0		/* If sizeof(int) == sizeof(char *) */
> or
>	#define NULL 0L		/* if sizeof(long) == sizeof(char *) */

Either is technically legal.  The former suffices, is not machine
dependent, and is correct.

>Since the above #defines are necessarily machine (and compiler) specific,

The definition of NULL (as 0, or if you have a dpANS-conformant
compiler, as (void *)0) is not machine dependent.  What *is* machine
dependent is whether uncasted 0 in unprototyped function calls appears
to work.  Using uncasted 0L appears to work (but is nonetheless wrong)
on IBM PCs using large model compilers.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

jas@llama.rtech.UUCP (Jim Shankland) (03/11/88)

In article <3351@chinet.UUCP> dag@chinet.UUCP (Daniel A. Glasser) writes:
>Your use of NULL === 0 promotes unportable code, and is considered to be
>bad programming style in a world where portability across multiple machine
>architectures ... is required....  On machines were sizeof int !=
>sizeof(void *), the above definition will
>not work....  A better way to do it, in each place you want to define NULL,
>is:
>
>	#ifndef	NULL
>	#define	NULL	((void *)0)
>	#endif

Ack!  Here it comes again!  Warm up the ``kill'' file.
Jim Shankland
  ..!ihnp4!cpsc6a!\
               sun!rtech!jas
 ..!ucbvax!mtxinu!/