dave@sds.UUCP (dave schmidt x194) (04/22/87)
In article <17005@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: > > [... commentary on problems w/ news s/w ...] also present > > are things like: > > charptr = malloc(1024); > > if ( !charptr ) ... /* this should be (charptr != (char *)NULL) */ > > This last one falls under the heading of "stupid problems from > [censored] compiler writers who didn't understand C before inflicting > something that they claim is a C compiler on Xenix users." > > if (!charptr) ... > > is IDENTICAL in meaning to > > if (charptr == 0) ... > > which, in turn, is IDENTICAL in meaning to > > if (charptr == (char *)0) ... > > if "charptr" is of type "char *". Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? This would not work on a machine where sizeof(char *) > sizeof(int). In fact, since we are comparing apples (char *'s) to oranges (int's), a cast is called for and a good, conscientious programmer would perform the cast themself. Furthermore, "if (!charptr)" is not correct on all architectures: I've heard tell of an implementation of C where NULL was 0xffffffff -- the simple "if (!charptr)" fails in this case. Coding the above as "if (charptr == (char *)NULL)" saves you from the "queer machine"; it is also more correct in that it explicitly states what you desire and is more portable. Actually, if NULL is *correctly* defined in stdio.h as "(char *)0" (NULL being the value that malloc() returns on error, NULL *should* have type char *), this could be written as "if (charptr == NULL)". Unfortunately for me, XENIX correctly defines NULL, leaving me to deal with statements such as: int cnt; char ch, *ptr; ptr = NULL; /* this is ok */ ... ch = NULL; /* expands to: ch = (char *)0; */ /* ch = '\0'; is desired */ ... if (cnt == NULL) /* expands to: if (cnt == (char *)0) */ /* i'd settle for if (!cnt) */ ... Given my #define for NULL, statements of this kind make the compiler complain (as it should). That point aside, it makes little sense to me to say that a pointer to a character and a character may be assigned the same value without a cast someplace. This implicitly assumes that NULL is #defined as 0, which is an assumption that good programmers (or should I say "programmers that I regard as good"?) don't make. Dave Schmidt (A firm believer in defensive programming.)
jss@hector.UUCP (04/23/87)
In article <146@sds.UUCP> dave@sds.UUCP writes: > >Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? Because the language definition says it isn't. The relevant paragraphs of K&R and ANSI proposal (I don't have H&S at hand) K&R: A pointer may be compared to an integer, but the result is machine dependent unless the integer is the constant 0. A pointer to which 0 has been assigned is guaranteed not to point to any object and will appear to be equal to 0 ... ANSI: An integral constand expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is assigned to or compared for equality to a pointer, the constant is converted to a pointer of that type ... Jerry Schwarz
gwyn@brl-smoke.UUCP (04/23/87)
In article <146@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: -In article <17005@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: -> > [... commentary on problems w/ news s/w ...] also present -> > are things like: -> > charptr = malloc(1024); -> > if ( !charptr ) ... /* this should be (charptr != (char *)NULL) */ -> -> This last one falls under the heading of "stupid problems from -> [censored] compiler writers who didn't understand C before inflicting -> something that they claim is a C compiler on Xenix users." -> -> if (!charptr) ... -> -> is IDENTICAL in meaning to -> -> if (charptr == 0) ... -> -> which, in turn, is IDENTICAL in meaning to -> -> if (charptr == (char *)0) ... -> -> if "charptr" is of type "char *". - - -Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? Because that's not what the C language specification says happens for such a comparison. -This would not work on a machine where sizeof(char *) > sizeof(int). -In fact, since we are comparing apples (char *'s) to oranges (int's), -a cast is called for and a good, conscientious programmer would perform -the cast themself. No cast is needed for the 0; 0 has special pointer properties in addition to its use as an integer constant. - Furthermore, "if (!charptr)" is not correct on all -architectures: I've heard tell of an implementation of C where NULL was -0xffffffff -- the simple "if (!charptr)" fails in this case. Another person has made the mistake of contradicting Guy in an area he is expert in. Guy's explanation is EXACTLY RIGHT independent of machine architecture or implementation, including bit pattern used to represent a null pointer (always written as 0 at the C source level regardless of the implementation). He, I, and others have explained more than once before in the C newsgroup that the only fully correct definition of the macro NULL, as C now stands, is as 0. (Under the proposed ANSI standard you could probably get away with defining it as (void *)0.) I will agree that writing if ( !pointer ) is bad style, but it is nonetheless technically correct. If your compiler can't handle this, then it is broken; perhaps you should try to get it fixed.
guy@gorodish.UUCP (04/23/87)
> Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? From K&R, 7.7 "Equality operators": A pointer may be compared to an integer, but the result is machine dependent unless the integer is the constant 0. A pointer to which 0 has been assigned is guaranteed not to point to any object, and will appear to be equal to 0; in conventional usage, such a pointer is considered to be null. So a C compiler *must* properly handle "if (charptr == 0)". It MUST not do an integer comparison if that would give a different result from a pointer comparison. In effect, it must convert the "0" to a character pointer of the appropriate type. On the other hand, *your* example converts the *pointer* to an *integer* before doing the comparision, which is NOT correct. > In fact, since we are comparing apples (char *'s) to oranges (int's), > a cast is called for and a good, conscientious programmer would perform > the cast themself. Wrong. The C language lacks a token like Pascal's "nil", so a constant expression with the value 0 must do double duty as a representation of a null pointer. The statement if (charptr == 0) is comparing "charptr" to a null pointer, not to the integer 0. > Furthermore, "if (!charptr)" is not correct on all architectures: Yes, it is. > I've heard tell of an implementation of C where NULL was > 0xffffffff -- the simple "if (!charptr)" fails in this case. If you mean "an implementation of C where 'NULL' is defined to be '0xffffffff'", you haven't heard of such an implementation, because it does not exist. What you heard of is an implementation of some other programming language, created by the implementor, which said implementor was under the delusion was C. If you mean "an implementatiion of C where a null pointer is represented by the same bit pattern that the integral constant '0xffffffff' has", then "if (!charptr)" MUST NOT FAIL. In a C implementation the statement "if (!charptr)" MUST compile into something that compares the variable "charptr" against a null pointer; if this means it must compare into a comparison of the bit pattern of "charptr" against the bit pattern represented by "0xffffffff", then so be it. > Coding the above as "if (charptr == (char *)NULL)" saves you from the > "queer machine"; No, it doesn't. It may save you against the turkey who didn't understand C when they implemented the compiler for that machine, but the right thing to do here is bitch that the alleged C implementation for that machine isn't implementing C. > it is also more correct in that it explicitly states what you desire I desire to compare "charptr" with a null pointer of the appropriate type; according to K&R (and the ANSI C draft), "if (charptr == 0)" quite explicitly requests that comparison. > and is more portable. Again, it is NOT more portable, unless you consider portability to incorrect C implementations to be important (I don't; I consider it more important to get those implementations fixed!). > Actually, if NULL is *correctly* defined in stdio.h as "(char *)0" > (NULL being the value that malloc() returns on error, NULL *should* have > type char *), Defining NULL as "(char *)0" is NOT correct. Other routines than "malloc" return NULL on errors, such as the UNIX routine "getpwent"; singe "getpwent" does not return a character pointer, it can hardly return "(char *)0". It seems to be a common misconception that the fragment if (foo == 0) or the expression foo = 0 must be implemented by testing whether all the bits representing "foo" are 0, or by clearing all the bits in "foo", respectively. This is simply not the case. For those of you still having trouble with this in the case where "foo" is a pointer, consider the case where "foo" is a floating-point number. There may be a machine on which floating-pointer zeroes do not have an all-zero bit pattern, and on those machines the aforementioned comparison and assignment cannot be implemented in the aforementioned fashion. Even if floating-point zeroes *do* have an all-zero bit pattern, consider the fragment if (foo == 1) or the expression foo = 1 Must these test whether all bits of "foo" are zero except for the low-order one, which must be one, or clear all bits of "foo" except for the low-order one, which is set? Of course not. If a not-necessarily-compatible successor to C were being designed, I'd recommend that they get rid of the pointer/array interchangability rules and add a token that represents a null pointer; both of those seem to cause C to behave in ways not obvious to novices.
blarson@castor.usc.edu.UUCP (04/24/87)
In article <146@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: >In article <17005@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: >Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? Because it is identical to if(charptr == (char *)0) by the specifications of the C language. (K&R, H&S, and ANSI all agree on this.) They differ because >This would not work on a machine where sizeof(char *) > sizeof(int). >In fact, since we are comparing apples (char *'s) to oranges (int's), >a cast is called for and a good, conscientious programmer would perform >the cast themself. And a good concientions compiler writer would write a compiler that compiles the langue in question, not something vaugly resembling it. >Furthermore, "if (!charptr)" is not correct on all >architectures: I've heard tell of an implementation of C where NULL was >0xffffffff -- the simple "if (!charptr)" fails in this case. Such architecutal oddities MUST be covered by the compiler/runtime system. A cast from an int to a pointer NEEDS to convert 0 to the approprate unusable pointer. Redefining NULL does not redefine C. A compiler that does not handle this is not a C compiler. >Coding the above as "if (charptr == (char *)NULL)" saves you from the >"queer machine"; it is also more correct in that it explicitly states what >you desire and is more portable. Writing code to avoid everything that is broken in some compiler is impossible. Why bother trying? >Actually, if NULL is *correctly* defined >in stdio.h as "(char *)0" (NULL being the value that malloc() returns >on error, NULL *should* have type char *), this could be written as >"if (charptr == NULL)". No, NULL should be defined as 0. Comparisons and assignments to ANY pointer type then work correctly, by definition. Remember, "if(intptr == NULL)" should work too, and "int *" and "char *" are NOT the same type. >Unfortunately for me, XENIX correctly defines >NULL, leaving me to deal with statements such as: You mean uncorrectly. [example deleted] >This implicitly >assumes that NULL is #defined as 0, which is an assumption that >good programmers (or should I say "programmers that I regard as good"?) >don't make. Yes, there are some broken stdio.h's out there, and NULL should be used only as a pointer value. >Dave Schmidt > >(A firm believer in defensive programming.) You avoid the bugs in your compiler, I'll avoid the bugs in mine. Don't expect me to agree that the language has been redefined by a broken compiler. -- Bob Larson Arpa: Blarson@Usc-Eclb.Arpa Uucp: (several backbone sites)!sdcrdcf!usc-oberon!castor.usc.edu!blarson seismo!cit-vax!usc-oberon!castor.usc.edu!blarson
greg@utcsri.UUCP (04/24/87)
In article <146@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: >Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? >This would not work on a machine where sizeof(char *) > sizeof(int). >In fact, since we are comparing apples (char *'s) to oranges (int's), >a cast is called for and a good, conscientious programmer would perform >the cast themself. Furthermore, "if (!charptr)" is not correct on all >architectures: I've heard tell of an implementation of C where NULL was >0xffffffff -- the simple "if (!charptr)" fails in this case. Even if NULL is 0xCAFEF00D, the !ptr method should still work. The constant '0' may be used in a pointer context in a very few cases ( assignment, initialization, comparison, opposite side of a : from a pointer ), and in each of these cases, the constant is taken to mean a null pointer of the appropriate type. When a pointer appears in conditional context, it is first converted to a comparison with zero. Thus !ptr becomes !(ptr!=0) which becomes ptr==nullptr where nullptr is a constant of the same type as ptr and with the same bit pattern as a null pointer of that type. If the null pattern is indeed zero, the instruction will be coded as test-for-zero. I am not sure whether this is what ANSI says, but I certainly think it should work this way. I have never used a machine where NULL has not been really 0, so I haven't run into a problem here. In any case K&R compilers allow '0' in any pointer context, and do not allow any other int constant without giving at least a warning. Even if a null char * had the value 0xCAFEF00D, it would be erroneous to say "charptr = 0xCAFEF00D". >Coding the above as "if (charptr == (char *)NULL)" saves you from the >"queer machine"; it is also more correct in that it explicitly states what >you desire and is more portable. Actually, if NULL is *correctly* defined >in stdio.h as "(char *)0" (NULL being the value that malloc() returns >on error, NULL *should* have type char *), this could be written as >"if (charptr == NULL)". This technique is much less portable. What if I have a structure pointer strp which I want to set to null? I can't say strp=NULL if NULL is incorrectly defined as (char*)0 since I get a type mismatch. I have to say strp=(struct foo*)NULL. What then if NULL is non-zero? I get strp=(struct foo*)(char*)0xCAFEF00D. Suppose the machine requires some pointer casts to convert. Do these casts convert? Suppose there is a different bit pattern for null char*'s and other null pointers. The best way to handle all these problems is to allow the compiler to convert the constant zero to the appropriate null pointer value. There is one little problem with this, that of passing null pointers to functions. A K&R compiler does not know the types of function parameters, so saying foo(0) when foo is expecting a int * won't work. You have to write foo((int*)NULL), which is the same as foo((int*)0). It follows that a compiler must perform casts of the constant zero to a pointer at compile time, by converting it to the appropriate null pointer. >Unfortunately for me, XENIX correctly defines >NULL, leaving me to deal with statements such as: > > int cnt; > char ch, *ptr; > > ptr = NULL; /* this is ok */ > ... > ch = NULL; /* expands to: ch = (char *)0; */ > /* ch = '\0'; is desired */ > ... > if (cnt == NULL) /* expands to: if (cnt == (char *)0) */ > /* i'd settle for if (!cnt) */ > ... The latter two usages of NULL are as you say incorrect. This is a human problem and not a compiler problem. > >Given my #define for NULL, statements of this kind make the compiler >complain (as it should). but it also complains about struct_ptr = NULL. There are other types of pointers than char *. > That point aside, it makes little sense to >me to say that a pointer to a character and a character may be >assigned the same value without a cast someplace. This implicitly >assumes that NULL is #defined as 0, which is an assumption that >good programmers (or should I say "programmers that I regard as good"?) >don't make. Good programmers don't use NULL unless they mean a null pointer. This is simply a matter of discipline. Other than that, they know that NULL is defined as 0 and that they therefore must cast it when passing it to a function. -- ---------------------------------------------------------------------- Greg Smith University of Toronto UUCP: ..utzoo!utcsri!greg Have vAX, will hack...
dave@sds.UUCP (dave schmidt x194) (04/24/87)
In article <2440@ulysses.homer.nj.att.com>, jss@hector..UUCP (Jerry Schwarz) writes: > In article <146@sds.UUCP> dave@sds.UUCP writes: > > > >Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? > > Because the language definition says it isn't. The relevant paragraphs > of K&R and ANSI proposal (I don't have H&S at hand) > > K&R: A pointer may be compared to an integer, but the result is > machine dependent unless the integer is the constant 0. A pointer to > which 0 has been assigned is guaranteed not to point to any object > and will appear to be equal to 0 ... OK, so the language provides that 0 has magical properties; even without this statement from K&R, I would expect the correct cast to made. However, as a matter of style, I personally prefer to explicitly cast the 0 to type char *. This, I admit, comes from working with brain-damaged compilers that don't always the correct conversion; some recent C compilers for the PC family don't properly compare 32-bit pointers to 16-bit int's. Only the low 16-bits are checked, so you can get bit if you don't do the cast yourself. At this point, many of you would say "It's a compiler problem, not my problem". Well, if that's the only C compiler you have at your disposal, it is your problem. (I personally would not fork out a couple of hundred bucks just to avoid doing a cast here and there.) Hopefully, the company will soon correct the problem and issue a fix in the next release. However, would you go to a client a say : "I'm going to be a month late for the deadline because the compiler is brain-damanged"? The fact of the matter is that language specification or no, some compilers do not produce code to conform to the specification. Since you could be unlucky enough to get inflicted with one, it's a matter of good style & defensive programming to protect yourself from such problems. But what about K&R saying "A pointer to which 0 has been assigned is guaranteed not to point to any object..."? I think an exception exists to this within the standard UNIX C library. Signal() returns a pointer to a function; in the event of an error, -1 is returned, not 0. As far as I know, all other functions that return a pointer, return 0 on error. This seems to imply that 0 may be a valid function address; not being familiar with UNIX internals, I can't say for certain, but other O/S's load programs such that they regard their starting address as 0. Anyway, if function f() has starting address 0, and I cast f's address to char *, that pointer points to something, no? On the other hand, if 0 cannot be a valid function address, why does signal not return 0 on error? I would be curious to know. Dave Schmidt
john@viper.UUCP (John Stanley) (04/24/87)
In article <5787@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes: > >He, I, and others have explained more than once before in the C newsgroup >that the only fully correct definition of the macro NULL, as C now stands, >is as 0. (Under the proposed ANSI standard you could probably get away >with defining it as (void *)0.) > Actualy, you -must- use (void *)0 -OR- 0 and be consistant one way or the other. It's not an option... If I had to choose, I'd say use #define NULL ((void*)0) because it's used as a pointer in 98% of the code I've seen and for the following reason... This isn't important on machines where sizeof(pointer) == sizeof(int), but if you're using (as a simple example) a machine where pointers are 32 bit and ints are 16 bits then code containing the following function may have problems if you change how NULL is defined: char *myfunc(ind, str, ind2) int ind, ind2; char *str; { char mystr[80]; if (str == NULL) str = mystr; ...... some code ..... return (str); } now, later in my program if I have NULL defined as 0, then the following line will cause the wrong number of bytes to be pushed on the stack: myfunc( i1, NULL, i2 ); If you use NULL as a pointer, it -must- always actualy be a pointer or your code will break any time you try to pass it as a constant to a function. You may claim, if you wish, that passing NULL as a constant is illegal, but I've seen too many examples of code where it's been done to accept that as a valid argument. If there already exists a large body of code using a particular construct, then that construct may be "illegal" in your opinion, but anyone discussing "the only fully correct definition" must take it into account... --- John Stanley (john@viper.UUCP) Software Consultant - DynaSoft Systems UUCP: ...{amdahl,ihnp4,rutgers}!{meccts,dayton}!viper!john
john@viper.UUCP (John Stanley) (04/24/87)
In article <17245@sun.uucp> guy%gorodish@Sun.COM (Guy Harris) writes: >> Why is "if (charptr == 0)" NOT identical to "if ((int)charptr == 0)" ??? > >a C compiler *must* properly handle "if (charptr == 0)". It MUST >not do an integer comparison if that would give a different result >from a pointer comparison. In effect, it must convert the "0" to a >character pointer of the appropriate type. > >On the other hand, *your* example converts the *pointer* to an >*integer* before doing the comparision, which is NOT correct. > Correct, but more importantly, casting the pointer to an int will truncate the pointer to whatever the number of bits an int would have on that system. This means, on a machine with 16 bit int's and 32 bit pointers, that any pointer which lies on a 64k boundry would test as equal to NULL which is obviously -w.r.o.n.g-. > > The C language lacks a token like Pascal's "nil", so a >constant expression with the value 0 must do double duty as a >representation of a null pointer. Wrong... Ever hear of something called "NULL"? K&R, Page 97: "We write NULL instead of zero, however, to indicate clearly that this is a special value for a pointer."... >> (!charptr) isn't portable > >Yes, it is. Although you may find one or two compilers which balk at this construct, it is correct on all "architectures". The compiler writers just need to fix their implementations on this one... --- John Stanley (john@viper.UUCP) Software Consultant - DynaSoft Systems UUCP: ...{amdahl,ihnp4,rutgers}!{meccts,dayton}!viper!john
dan@prairie.UUCP (Daniel M. Frank) (04/25/87)
In article <2440@ulysses.homer.nj.att.com> jss@hector (Jerry Schwarz) writes: >K&R: A pointer may be compared to an integer, but the result is >machine dependent unless the integer is the constant 0. A pointer to >which 0 has been assigned is guaranteed not to point to any object >and will appear to be equal to 0 ... Oh. The VAX compiler and linker appear to arrange that (char *)0 ALWAYS points to a null string. Other systems do the same (I believe HP/UX does, given the unportability of code in a popular user-friendly mail agent published on the this network). If the VAX would have the decency to fault on null pointer dereferencing, a lot of code written on it would be a heck of a lot more portable. -- Dan Frank (w9nk) ARPA: dan@db.wisc.edu ATT: (608) 255-0002 (home) UUCP: ... uwvax!prairie!dan (608) 262-4196 (office) SNAILMAIL: 1802 Keyes Ave. Madison, WI 53711-2006
dan@prairie.UUCP (Daniel M. Frank) (04/25/87)
In article <5787@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes: >He, I, and others have explained more than once before in the C newsgroup >that the only fully correct definition of the macro NULL, as C now stands, >is as 0. (Under the proposed ANSI standard you could probably get away >with defining it as (void *)0.) No. On the 8086 family, in large model, if NULL is defined as 0, then such things as execl(..., NULL) ; will fail, because a pointer (32-bit) parameter is expected, and an int (16-bit) parameter is passed, with some garbage in the stack making up the difference. Lint will not catch this, either, because execl is /*VARARGS*/. It's pretty safe to define NULL as 0L when in large model, but the best solution of all is (void *)0, or type-specific NULLs (i.e. NULLCHAR). The (void *)0 solution may cause problems with lint, though. -- Dan Frank (w9nk) ARPA: dan@db.wisc.edu ATT: (608) 255-0002 (home) UUCP: ... uwvax!prairie!dan (608) 262-4196 (office) SNAILMAIL: 1802 Keyes Ave. Madison, WI 53711-2006
wcs@ho95e.ATT.COM (Bill.Stewart) (04/25/87)
In article <5787@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes: :In article <146@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: :-In article <17005@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: :-> This last one falls under the heading of "stupid problems from :-> [censored] compiler writers who didn't understand C before inflicting :-> something that they claim is a C compiler on Xenix users." :[ ... much discussion by everyone about differences between :-> if (!charptr) ... /* Ugly but correct, says Doug */ :-> if (charptr == 0) ... /* Best */ :-> if (charptr == (char *)0) ... /* Almost always ok */ :- if ((int)charptr == 0) /* WRONG, of course */ :] :He, I, and others have explained more than once before in the C newsgroup :that the only fully correct definition of the macro NULL, as C now stands, :is as 0. (Under the proposed ANSI standard you could probably get away :with defining it as (void *)0.) Be careful saying this: it's only correct when you're using NULL in if (charptr == NULL) But (as Guy periodically has to remind VAX-users), you can't simply use NULL in constructs like: foo( arg1, arg2, NULL, arg4); because subroutine foo() might be expecting an argument of a different size. If you #define NULL 0, then you pass foo sizeof(int) bytes; if you #define NULL (void *)0, then you pass it sizeof(void *) bytes. If you're passing NULL (or other non-int constants), always cast to foo( arg1, arg2, (whatever_type) NULL, arg4); so the routine receives what you passed. -- # Bill Stewart, AT&T Bell Labs 2G-202, Holmdel NJ 1-201-949-0705 ihnp4!ho95c!wcs
gwyn@brl-smoke.ARPA (Doug Gwyn ) (04/26/87)
In article <148@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: >... some recent C compilers for the >PC family don't properly compare 32-bit pointers to 16-bit int's. Only >the low 16-bits are checked ... There is no such thing as a "proper" way to do this! The compiler may do anything, including terminating compilation with a fatal error. Why are you trying to do such a thing in the first place? >But what about K&R saying "A pointer to which 0 has been assigned is >guaranteed not to point to any object..."? This is meant to guarantee that the address of any object will not match a 0 pointer, so that 0 can have the reserved special meaning of a null pointer without problems arising. For a straightforward implementation of C on a nice regular architecture, this means that address 0 in the process address space must not be assigned to any visible object by the linker. For UNIX systems, this is done by reserving storage at that address within the run-time startoff header module that is linked at the beginning of user processes. The linker could also be kludged up to start psect assignments at I or D address 1, 2, or whatever. >On the other hand, if 0 cannot be a valid function address, why does >signal not return 0 on error? This is an artifact from the good old days when PDP-11 UNIX was the only relevant C implementation. Integer 0 == pointer 0 was used for SIG_DFL and value 1 was used for SIG_IGN; being a system call, the common error return module "cerror" was used, and it always returned integer -1. The "integer equivalents" 0, 1, and -1 for what are properly (void (*)()) cannot be guaranteed to be meaningful on all architectures, which is why you should use the macros SIG_DFL, SIG_IGN, and SIG_ERR instead. (Some systems don't yet define SIG_ERR, but X3J11 and IEEE 1003.1 require it.)
gwyn@brl-smoke.ARPA (Doug Gwyn ) (04/26/87)
In article <888@viper.UUCP> john@viper.UUCP (John Stanley) writes: > myfunc( i1, NULL, i2 ); > >If you use NULL as a pointer, it -must- always actualy be a pointer or >your code will break any time you try to pass it as a constant to a >function. No matter what NULL is defined as, the above usage is non-portable and will break on some systems. The problem is that the widths of pointers of various types are in general different (and different from an (int)), so that the wrong parameter alignment will occur unless one happens to be lucky. If you pass NULL as a function parameter, you should always cast it to the correct pointer type, as in myfunc( i1, (struct foo *)NULL, i2 ); It is certainly true that there is a lot of code that makes this mistake; I must have fixed several hundred occurrences of this particular bug by now. That does not make it any less erroneous. For implementations of the draft proposed American National Standard for C, if a function prototype happens to be in scope, the compiler is required to automatically convert the parameters to have types that match the prototype. That would let you get away with such sloppy coding practice without ever realizing what is happening, which is why some of us are opposed to automatic parameter type coercion.
henry@utzoo.UUCP (Henry Spencer) (04/27/87)
> No. On the 8086 family, in large model, if NULL is defined as 0, then > such things as > > execl(..., NULL) ; > > will fail, because a pointer (32-bit) parameter is expected... Quite true, because the code is incorrect. The correct way to write this, obsolete manuals notwithstanding, is: execl(..., (char *)NULL); > It's pretty safe to define NULL as 0L when in large model, but the best > solution of all is (void *)0... In correct, portable pre-X3J11 C, THERE IS NO WAY TO AVOID THE char * CAST! The problem is that pointers of different types are not even guaranteed to have the same size, much less the same representation, so there is no, repeat *NO*, "generic NULL pointer". This doesn't matter much except at the function-call interface, since the compiler will get it right elsewhere if NULL is just 0 (and if the compiler is compiling C, not some peculiar variant of it)... but at the function-call interface it matters very much and there is simply no substitute for typing a few extra characters to get it right! It is popular to botch this. That does not make it right. -- "If you want PL/I, you know Henry Spencer @ U of Toronto Zoology where to find it." -- DMR {allegra,ihnp4,decvax,pyramid}!utzoo!henry
goudreau@dg_rtp.UUCP (Bob Goudreau) (04/27/87)
In article <889@viper.UUCP> john@viper.UUCP (John Stanley) writes: >In article <17245@sun.uucp> guy%gorodish@Sun.COM (Guy Harris) writes: > > > > The C language lacks a token like Pascal's "nil", so a > >constant expression with the value 0 must do double duty as a > >representation of a null pointer. > > Wrong... Ever hear of something called "NULL"? > >K&R, Page 97: > "We write NULL instead of zero, however, to indicate clearly that >this is a special value for a pointer."... Sorry, but Guy is correct. NULL isn't a feature of C anymore than EOF, stdin or stdout are; these are just literals defined in stdio.h. The preprocessor converts these references to the appropriate constants, so the compiler sees only the "bare bones" C. -- Bob Goudreau Data General Corp. 62 Alexander Drive Research Triangle Park, NC 27709 (919) 248-6231 ...!mcnc!rti-sel!dg_rtp!goudreau
greg@utcsri.UUCP (04/27/87)
In article <889@viper.UUCP> john@viper.UUCP (John Stanley) writes: > > The C language lacks a token like Pascal's "nil", so a > >constant expression with the value 0 must do double duty as a > >representation of a null pointer. > > Wrong... Ever hear of something called "NULL"? > >K&R, Page 97: > "We write NULL instead of zero, however, to indicate clearly that >this is a special value for a pointer."... But NULL is #defined as 0, so this is nothing but a commenting device in K & R C. The two classic examples where it fails to work are: func_expecting_char_ptr(NULL); func(0) causes an *int* to be pushed. char_ptr = a<b? NULL: NULL; The RHS of the assignment is not a constant with type int and value 0, so the above is not legal ( as opposed to "a<b? char_ptr:0 ", in which the 0 is converted to a null char * by virtue of it being across the : from one ). Both of these can be fixed by the up-and-coming semantics of void* (NULL being defined as ((void*)0) ), and function prototypes won't hurt. -- ---------------------------------------------------------------------- Greg Smith University of Toronto UUCP: ..utzoo!utcsri!greg Have vAX, will hack...
mike@turing.unm.edu (Mike Bushnell) (04/27/87)
In article <148@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: >... >On the other hand, if 0 cannot be a valid function address, why does >signal not return 0 on error? I would be curious to know. > >Dave Schmidt Because *every* function in section 2 of the manual returns -1 on an error. It just happens that signal is one of the very few which normally return pointers. If compiler writers were *really* nasty, they could make this bomb. Fortunately, they are on our side. I like to cast signal to an int before checking for -1 just to be on the safe side. Michael I. Bushnell a/k/a Bach II hi!turing!mike@hc.dspo.gov
guy@gorodish.UUCP (04/27/87)
>Signal() returns a pointer to a function; in the event of an error, -1 is >returned, not 0. As far as I know, all other functions that return a >pointer, return 0 on error. This seems to imply that 0 may be a valid >function address; No, you're inferring (incorrectly) that 0 may be a valid function address. >not being familiar with UNIX internals, I can't say for certain, but other >O/S's load programs such that they regard their starting address as 0. As UNIX is a portable operating system, it is not meaningful to ask whether UNIX loads programs such that they regard their starting address as 0. Some implementations do, some don't. (Ours doesn't.) However, just because a UNIX implementation loads a program such that it regards its starting address as 0 does NOT mean that the address of any function known to C within that program is necessarily 0. In fact, a C implementation is obliged to do whatever is necessary to ensure that no function in a program will have the address 0; a "nop" or a "br .+N" at location 0 should be sufficient. >On the other hand, if 0 cannot be a valid function address, why does >signal not return 0 on error? I would be curious to know. Because: 1) other system calls return -1 on error (including a System V shared memory call that returns a pointer); 2) the special signal value for "this signal is not caught" is usually a null pointer, so a call to "signal" that is successful can return a null pointer.
guy@gorodish.UUCP (04/27/87)
> >He, I, and others have explained more than once before in the C newsgroup > >that the only fully correct definition of the macro NULL, as C now stands, > >is as 0. (Under the proposed ANSI standard you could probably get away > >with defining it as (void *)0.) > > > >Actualy, you -must- use (void *)0 -OR- 0 and be consistant one way or >the other. It's not an option... Why? > char *myfunc(ind, str, ind2) > int ind, ind2; > char *str; ... >>now, later in my program if I have NULL defined as 0, then the following >line will cause the wrong number of bytes to be pushed on the stack: > > myfunc( i1, NULL, i2 ); 1) Your program will *also* have problems if you define NULL as "(void *)0", if "(char *)0" and "(void *)0" don't have the same representation. 2) Your program *won't* have problems if you defined "myfunc" as char * myfunc(int ind, char *str, int ind2) ... because the compiler would perform the coercion of 0 into (char *)0. >You may claim, if you wish, that passing NULL as a constant is illegal, >but I've seen too many examples of code where it's been done to accept >that as a valid argument. If there already exists a large body of code >using a particular construct, then that construct may be "illegal" in >your opinion, but anyone discussing "the only fully correct definition" >must take it into account... No, they don't. Calling "myfunc(i1, NULL, i2)" is illegal if NULL is defined as 0, and "lint" will tell you that. No implementor is obliged to make this illegal construct work.
guy@gorodish.UUCP (04/27/87)
> > The C language lacks a token like Pascal's "nil", so a > >constant expression with the value 0 must do double duty as a > >representation of a null pointer. > > Wrong... Ever hear of something called "NULL"? Wrong... Ever hear of something called a "preprocessor"? NULL is *not* a token defined by the language's grammar. It's a preprocessor definition, so it ultimately has to expand to *something*, and the only thing it can expand to is "0".
guy@gorodish.UUCP (04/27/87)
>Be careful saying this: it's only correct when you're using NULL in > if (charptr == NULL) Or if you're declaring and defining functions with prototype arguments, which the ANSI C standard (which Doug referred to) permits and recommends. If you use prototype arguments, the compiler will convert 0 or (void *)0 to the proper type.
guy@gorodish.UUCP (04/27/87)
> No. On the 8086 family, in large model, if NULL is defined as 0, then >such things as > > execl(..., NULL) ; > >will fail, So? That call is illegal. Yes, I know "lint" won't catch it, but "lint" won't catch attempts to dereference null pointers, either. > It's pretty safe to define NULL as 0L when in large model, but the best >solution of all is (void *)0, or type-specific NULLs (i.e. NULLCHAR). >The (void *)0 solution may cause problems with lint, though. The best solution of all is properly casting NULL when passing it as an argument.
dyer@spdcc.COM (Steve Dyer) (04/28/87)
Goodness knows there are enough things to complain about with the 286 chip and the XENIX 286 C compiler and libraries that attempt to use it, especially when you try to use the 286 large and huge memory models. However, the use of the (very common) constructs: char *charp; if (!charp) or if (charp == NULL) is NOT one of them; they work fine. Having wrestled now with SCO XENIX for more than a year, bereft of sources, I suspect the the original poster had a fit of paranoia; after trying to get anything to run on this machine, you begin doubting your tools, your choice of memory models, and finally your own understanding of the C language itself! Be kind... :-) News 2.11 patchlevel 8 runs just fine on SCO XENIX. Interested parties can get it from my machine: spdcc Any ACU {1200,2400,300} 16176614927 login: uucp uucp spdcc!~uucp/compress compress (12-bit compress) uucp spdcc!~uucp/sconews.tar.Z . ln compress uncompress uncompress sconews.tar I haven't turned this into a set of diffs between standard 2.11 patch 8 and this distribution, mainly because I haven't had the time. The only XENIX-specific changes are in the System V locking code which needed to be changed to use XENIX locking, because as implemented under SCO XENIX, System V locking is enforced against ALL processes, and not just those which use the lockf system calls. This means that locking the ACTIVE file (as during expire) loses. This is clearly wrong according to the SVID and all other System V implementations, but they passed the SVID test, so therefore it's OK (or so they say...) There's a file called XENIXCHANGES in the src subdirectory which explains what I did. -- Steve Dyer dyer@harvard.harvard.edu dyer@spdcc.COM aka {ihnp4,harvard,linus,ima,bbn,halleys}!spdcc!dyer
henry@utzoo.UUCP (Henry Spencer) (04/28/87)
> Both of these can be fixed by the up-and-coming semantics of void* (NULL being > defined as ((void*)0) ), and function prototypes won't hurt. Not quite correct: "void *" does ***NOT*** fix the func(NULL) problem, although function prototypes do, if consistently applied. (They have other problems, and I'm not sure they were a good idea, but they do solve this particular hassle after a fashion.) -- "If you want PL/I, you know Henry Spencer @ U of Toronto Zoology where to find it." -- DMR {allegra,ihnp4,decvax,pyramid}!utzoo!henry
karl@haddock.UUCP (04/28/87)
In article <148@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: >But what about K&R saying "A pointer to which 0 has been assigned is >guaranteed not to point to any object..."? I think an exception exists >to this within the standard UNIX C library. Signal() returns a pointer >to a function; in the event of an error, -1 is returned, not 0. This is no longer true (and should never have been true). The error return from ANSI signal is the constant SIG_ERR, and its value is, of course, not specified. (Although it happens to be ((void (*)(int))-1) on current UNIX implementations, there is no guarantee that this cast is meaningful in general.) >As far as I know, all other functions that return a pointer, return 0 on >error. System calls do not, for absurd historical reasons. (The other exceptions are sbrk and shmat.) Hopefully this will change soon. >On the other hand, if 0 cannot be a valid function address, why does >signal not return 0 on error? I would be curious to know. The function signal() has three out-of-band return values: SIG_ERR, SIG_DFL, and SIG_IGN. At most one of them can have the value ((void (*)(int))0). (In current implementations, it happens to be SIG_DFL; but this fact should be completely transparent to the programmer.) Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
karl@haddock.UUCP (04/28/87)
In article <476@unmvax.UNM.EDU> mike@turing.UUCP (Mike Bushnell) writes: >I like to cast signal to an int before checking for -1 ... That could still fail; consider a two's complement machine with 16-bit ints, 32-bit function pointers, and a valid function at address 0x1000ffff. It is more correct to cast the -1 into an appropriate pointer. Even this isn't perfect (for several reasons), which is why ANSI has introduced the constant SIG_ERR to compare against. Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
allbery@ncoast.UUCP (04/29/87)
As quoted from <5787@brl-smoke.ARPA> by gwyn@brl-smoke.ARPA (Doug Gwyn ): +--------------- | I will agree that writing | if ( !pointer ) | is bad style, but it is nonetheless technically correct. If your compiler | can't handle this, then it is broken; perhaps you should try to get it fixed. +--------------- HAH!!! The System V.2 pcc is the first version that gets this correct consistently; in fact, it's the first version that gets "if (!x)" for any x which may not necessarily be constrained to values in the set {0, 1} correct. (You would not believe the problems I've had bringing up phantasia under Xenix 2.3 and 3.0 for Tandy 6000 and under System III for Plexus, due to this.) As a result, whether it's ``technically correct'' or not I refuse to use that feature in my own programs, in the interests of allowing people using old pcc's to compile my programs. I don't know what the heck pcc was doing before (or what it's doing now; I wouldn't trust it as far as I could throw it) but we aren't the only ones that use it. I just pray daily for Plexus to release V.3 with the Green Hills compiler. ++Brando -- Brandon S. Allbery {decvax,cbatt,cbosgd}!cwruecmp!ncoast!allbery Tridelta Industries {ames,mit-eddie,talcott}!necntc!ncoast!allbery 7350 Corporate Blvd. necntc!ncoast!allbery@harvard.HARVARD.EDU Mentor, OH 44060 +01 216 255 1080
m5d@bobkat.UUCP (04/29/87)
In article <148@sds.UUCP> dave@sds.UUCP (dave schmidt x194) writes: > . . . This, I admit, comes from working with brain-damaged compilers >that don't always the correct conversion; some recent C compilers for the >PC family don't properly compare 32-bit pointers to 16-bit int's. Only >the low 16-bits are checked, so you can get bit if you don't do the >cast yourself. At this point, many of you would say "It's a compiler >problem, not my problem". . . . > >Dave Schmidt Well, at this point I would say that you're confused if you think that comparison between int and pointer variables is defined at all. The only "integer" value defined to be usable in pointer comparisons is zero. It is NOT OK to compare an integer variable directly to a pointer. It is ok to cast the int explicitly to a pointer and then compare, but I don't know what this means on an iAPX 86-based compiler. I don't know what it would use for a segment value; probably zero, but it might use the current DS. In any case you'd probably want a library function to construct a pointer from two integers (or a long I guess). -- Mike McNally, mercifully employed at Digital Lynx --- Where Plano Road the Mighty Flood of Forest Lane doth meet, And Garland fair, whose perfumed air flows soft about my feet... uucp: {texsun,killer,infotel}!pollux!bobkat!m5d (214) 238-7474
allbery@ncoast.UUCP (05/01/87)
As quoted from <442@prairie.UUCP> by dan@prairie.UUCP (Daniel M. Frank): +--------------- | In article <2440@ulysses.homer.nj.att.com> jss@hector (Jerry Schwarz) writes: | >K&R: A pointer may be compared to an integer, but the result is | >machine dependent unless the integer is the constant 0. A pointer to | >which 0 has been assigned is guaranteed not to point to any object | >and will appear to be equal to 0 ... | | Oh. The VAX compiler and linker appear to arrange that (char *)0 | ALWAYS points to a null string. Other systems do the same (I believe | HP/UX does, given the unportability of code in a popular user-friendly | mail agent published on the this network). If the VAX would have the +--------------- Huh??? Our computer's linker (Plexus P/60, System V; Plexus P/35, System III on ncoast also) starts data segment allocatioon at address 2, so pointers to 0 and 1 are guaranteed to cause core dumps; Elm came up fine on both (barring the fact that someone installed it on ncoast by copying elm to /usr/local/bin instead of running "make install"...) ++Brando -- Copyright (C) 1987 Brandon S. Allbery -- you can redistribute only if your recipients can. Brandon S. Allbery {decvax,cbatt,cbosgd}!cwruecmp!ncoast!allbery Tridelta Industries {ames,mit-eddie,talcott}!necntc!ncoast!allbery 7350 Corporate Blvd. necntc!ncoast!allbery@harvard.HARVARD.EDU Mentor, OH 44060 +01 216 255 1080
allbery@ncoast.UUCP (05/01/87)
As quoted from <17436@sun.uucp> by guy%gorodish@Sun.COM (Guy Harris): +--------------- | >>now, later in my program if I have NULL defined as 0, then the following | >line will cause the wrong number of bytes to be pushed on the stack: | > | > myfunc( i1, NULL, i2 ); | | 1) Your program will *also* have problems if you define NULL as | "(void *)0", if "(char *)0" and "(void *)0" don't have the same | representation. +--------------- Pardon me???! (void *) is a GENERIC pointer, is it not? It should, in theory, be a type pun in all situations (if casting the result of a rational malloc() (defined as returning (void *)) changes the representation of the pointer returned, we've got trouble, no?). +--------------- | 2) Your program *won't* have problems if you defined "myfunc" as | | char * | myfunc(int ind, char *str, int ind2) +--------------- If you have an X3J11 compiler, let me know; *mine* takes (void *) and proceeds to ignore the argument/variable from that point on (no error on the type, but the variable is not defined). Which compiler? Which machine? ++Brando -- Copyright (C) 1987 Brandon S. Allbery -- you can redistribute only if your recipients can. Brandon S. Allbery {decvax,cbatt,cbosgd}!cwruecmp!ncoast!allbery Tridelta Industries {ames,mit-eddie,talcott}!necntc!ncoast!allbery 7350 Corporate Blvd. necntc!ncoast!allbery@harvard.HARVARD.EDU Mentor, OH 44060 +01 216 255 1080
tim@ism780c.UUCP (Tim Smith) (05/09/87)
In article <17439@sun.uucp> guy@sun.UUCP (Guy Harris) writes:
< Or if you're declaring and defining functions with prototype
< arguments, which the ANSI C standard (which Doug referred to) permits
< and recommends.
Are there many compilers that allow this now, or are most people
waiting for the ANSI C standard to become real? LightspeedC on
the Mac allows it, but that is the only one I know of.
--
Tim Smith "Learn to juggle while it's still legal"
sdcrdcf!ism780c!tim