stevesu@copper.TEK.COM (Steve Summit) (06/23/87)
In article <6714@auspyr.UUCP>, mick@auspyr.UUCP (Mick Andrew) writes: > As far as typedefs go, I fall into the "anti" camp. When supporting > unfamiliar code, the following sequences drive me crazy > > func() > { > sometype var; > } > > Hmm, search for "sometype". In an (always obscure :-) include file we find > > typedef struct s sometype; > > Ah ha, "var" is actually a structure... now where is that structure > definition... you get the idea. By using a typedef (or even in this case > a #define), the program becomes less readable. And in article <5999@brl-smoke.ARPA>, gwyn@brl-smoke.ARPA (Doug Gwyn ) writes: > > extern func_ptr_func_int getfunc ; > > The problem with this is that anyone reading your code would have to > untangle the several levels of typedef in order to determine the meaning > of this declaration, whereas with > extern int (*getfunc())(); > the meaning of the declaration is patent. This is not a particularly > complicated construct; anyone who has to read C code should learn how > type specification works in C, after which this is easy to understand. It's unfortunate that people are finding reasons to deprecate typedefs, which can be used to substantially _i_n_c_r_e_a_s_e the readability and portability of code. The two complaints above follow from the fact that typedefs, like many features of C, can be (ab)used, with devastating effect, to make code absolutely impenetrable. Many of the things being discussed on this newsgroup (including especially the bizarre ideas about enums) simply wouldn't come up if people would be content to write their programs simply and straightforwardly, instead of treating every new programming opportunity as a chance to enter the obfuscated C hall of fame. If the program you are dealing with has been conscientiously written (and yes, I know, many programs are not) then the definition for "sometype" should be in some obviously-named header file like "sometype.h", and even if it's not obvious, it should require only a few seconds with grep to find it, if you really need to know. When you come across something like extern func_ptr_func_int getfunc ; it's a safe bet, especially when you glance down at how getfunc is used, that it is a pointer to a function returning an int, unless whoever wrote it was deliberately trying to make your life miserable. A friend of mine actually objects to #define TRUE 1 #define FALSE 0 because whenever he comes across something like while(TRUE) he claims he has to go fishing around to find out how TRUE is defined. Now, if you're dealing with a mentality that would do something like #define TRUE 0 /* don't try this at home, kids */ then you've got major problems, and it wouldn't help you if that misguided individual had avoided typedefs and/or #defines. (I will admit that the examples quoted in Mick's and Doug's postings could have been written more clearly, but the problem is not with the typedefs per se. Although typedeffed structures can certainly be confusing, they can also provide a useful abstraction, especially at a library interface, where you aren't supposed to have to know what the insides of the structure look like.) There appears to be a widespread notion, a sort of overzealous application of Occam's Razor, that programs are to be judged with the wc utility, and that, for a given algorithm, the source file with the fewest characters shall be deemed best. This leads to constructions like char *p; if(p) ... which are so endlessly discussed in this newsgroup. My only complaint with K&R, which is otherwise as close to prefection as I expect to find in this industry, is that it appears to condone that sort of usage: Although this may seem cryptic at first sight, the notational convenience is considerable, and the idiom should be mastered, if for no other reason than that you will frequently see it in C programs. [1] I have no idea what "notational convenience" is, but I suspect it has to do with keeping that character count down. The right thing to try to keep down in your programs is needless complexity, by doing everything you can to make the code comprehensible to someone (including yourself) who might have to make sense of it later. The well-considered #include, #define, typedef, or even (heavens!) goto can work wonders towards improving the readability of code. Discouraging the use of these constructs only makes it harder to write clean code, and doesn't really slow down the antagonistic programmers out there who seem to think they'll be violated if anyone can penetrate their code. It seems that there are two schools of thought on this. A large body of opinion (suggested by Doug's comment that "anyone who has to read C code should learn how type specification works," holds that, for us Real Programmers, any attempt to demystify C is akin to quiche-eating. Given the current state of the "art," it is true that everyone who wants to read existing C code should learn about type specification in all its gory detail, but I wish they didn't _h_a_v_e to. Let me make some confessions. I consider myself an excellent C programmer, but: a) I still don't completely understand _a_l_l the intricate nuances of the more obscure C types. I can usually come pretty close, but for the really strange ones, like char (*b)[6], I always check myself with a mindless automaton like cdecl. Interestingly enough, the _o_n_l_y time I have to use cdecl is when I'm reading the the fascinating but artificial posers in this newsgroup; those bizarre type constructions just don't come up that often in the real programs I work with. b) I actively prefer if(p != NULL) to if(!p) I know that they are 100% equivalent as far as a correct compiler is concerned (and they are; let's not get started on that again), and I have "mastered the idiom," but the second form takes enough extra thought, and is confusing enough to the uninitiated, that it just plain isn't worth the seven characters saved. The following quote, from _T_h_e _E_l_e_m_e_n_t_s _o_f _S_t_y_l_e, explains very well what I am trying to say: The practical objection to unaccepted and oversimplified spellings is the disfavor with which they are received by the reader. They distract his attention and exhaust his patience. He reads the form "though" automatically, without thought of its needless complexity; he reads the abbreviation "tho" and mentally supplies the missing letters, at a cost of a fraction of his attention. The writer has defeated his own purpose. [2] Now, I'm sure that a lot of you disagree with this (as I said, there seem to be two camps, and mine may be the minority), so please don't flame me on the network, unless you feel you must save others from my corrupting influence. (Private flames are always welcome, and occasionally replied to.) Steve Summit stevesu@copper.tek.com References: [1] Kernighan and Ritchie, _T_h_e _C _P_r_o_g_r_a_m_m_i_n_g _L_a_n_g_u_a_g_e, p. 101 [2] Strunk and White, _T_h_e _E_l_e_m_e_n_t_s _o_f _S_t_y_l_e, Third Edition, p. 75
ddl@husc6.UUCP (Dan Lanciani) (06/24/87)
In article <1158@copper.TEK.COM>, stevesu@copper.TEK.COM (Steve Summit) writes: | b) I actively prefer | | if(p != NULL) | to | if(!p) | | I know that they are 100% equivalent as far as a correct | compiler is concerned (and they are; let's not get | started on that again), and I have "mastered the idiom," | but the second form takes enough extra thought, and is | confusing enough to the uninitiated, that it just plain | isn't worth the seven characters saved. Are you absolutely sure that you have "mastered the idiom?" :-) (Sorry, I couldn't resist.) Dan Lanciani ddl@harvard.*
mick@auspyr.UUCP (Mick Andrew) (06/26/87)
in article <1158@copper.TEK.COM>, stevesu@copper.TEK.COM (Steve Summit) says:
[ All kinds of good stuff deleted. ]
Steve Summit made an excellent posting regarding writing clear, understandable
code. More good sense and useful observations than have been seen on the
net in many moons.
I agree 99.9% with all of his comments on coding style, especially
the awful habit of making code "better" by reducing the total character count.
In the spirit of
if(p != NULL) vs. if(!p)
I offer one of my favourite obfuscated one liners:
if (strcmp(s1, s2) == 0) vs. if (!strcmp(s1, s2))
In general, I try to use the yardstick,
"if it seems like a neat trick, don't do it!"
--
----
Mick Austec, Inc., San Jose, CA
{sdencore,necntc,cbosgd,amdahl,ptsfa,dana}!aussjo!mick
{styx,imagen,dlb,gould,sci,altnet}!auspyr!mick
chris@mimsy.UUCP (Chris Torek) (06/27/87)
In article <1158@copper.TEK.COM> stevesu@copper.TEK.COM (Steve Summit) writes: >It's unfortunate that people are finding reasons to deprecate >typedefs, which can be used to substantially *increase* the >readability and portability of code. The two complaints above >follow from the fact that typedefs, like many features of C, can >be (ab)used, with devastating effect, to make code absolutely >impenetrable. This connects with my own distrust of adding strong typing to C. Here is a real live example. Looking in <stdio.h>, we find struct _iobuf { ... }; ... #define FILE struct iobuf (Here the define is much like a typedef; either would do.) This virtually (but not in fact) adds a type to the set of types the programmer must understand. `FILE' is such a well-known type that no one has any more trouble with it than with `int'. For reasons that I shall not explain, I have decided to change the internals of stdio such that user code can `open' a set of I/O functions. Stdio will call these functions instead of `read' and `write', `seek', and `close'. For instance: struct info info; /* where to write, etc. */ void *p = &info; /* p will be given to writefn */ int writefn(); /* and writefn will write things */ FILE *fp = write_function_open(p, writefn); ... fprintf(fp, fmt, ...) ... would open `writefn' for writing, and would call `writefn(p, buf, n)' instead of `write(fileno(fp), buf, n)'. This can be used for a dynamically allocating `sprintf', or for a curses-style `wprintf', or whatever. But this is not properly typed: writefn() is not a function of no arguments. This: int writefn(void *p, const char *buf, int n); is the proper declaration. But stdio can do more than just write: it can read, and it can seek; and it eventually needs to close the `file'. So the general form is this: FILE *funopen(); Whoops, we forgot the types of the arguments. FILE *funopen(void *p, int (*readfn)(), int (*writefn)(), long (*seekfn)(), int (*closefn)()); All these parentheses are annoying, so let us add a few typedefs: typedef int (*iofun)(); typedef long (*seekfun)(); FILE *funopen(void *p, iofun readfn, iofun writefn, seekfun seekfn, iofun closefn); But wait, this is not right! The arguments to close are not the same as those to read! We need to declare *everything*, to get those types right. typedef int (*iorfp)(void *p, char *buf, int n); typedef int (*iowfp)(void *p, const char *buf, int n); typedef long (*ioseekfp)(void *p, long off, int whence); typedef int (*ioclosefp)(void *p); FILE *funopen(void *p, iorfp readfn, iowfp writefn, ioseekfp seekfn, ioclosefp closefn); Well, we finally did it. But look at the cost: Four typedefs just for the arguments to `funopen'. It really takes four; all four are different. We could get away with three, by lying and pretending that the write function is allowed to clobber the contents of *buf: typedef int (*iorwfp)(void *p, char *buf, int n); but if we are going to use strong typing at all, we should keep the two distinct. Well, we could discard the typedefs entirely, since they are just aliases and the declaration of an ior function will have to match the use of the iorfp: FILE *funopen( void *p, int (*readfn)(void *p, char *buf, int n), int (*writefn)(void *p, const char *buf, int n), long (*seekfn)(void *p, long off, int whence), int (*closefn)(void *p)); It is hard to say which is worse. This does not steal away any of the global namespace, as do the typedefs, but it is horribly monolithic. This demonstrates what happens with strongly typed systems: They lead to a profusion of types, and it becomes difficult to keep them straight. Does database_write call an iowfp function, or did we put a database type layer over it, and it calls a dbiowfp? Of course, these types are indeed different, and calling an iowfp function when you were supposed to call a dbiowfp is likely to be a drastic error. So all this strong typing may be good. But it certainly does not look like C anymore. -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690) Domain: chris@mimsy.umd.edu Path: seismo!mimsy!chris
g-rh@cca.CCA.COM (Richard Harter) (06/27/87)
In article <6858@auspyr.UUCP> mick@auspyr.UUCP (Mick Andrew) writes: > >I agree 99.9% with all of his comments on coding style, especially >the awful habit of making code "better" by reducing the total character count. > >In the spirit of > if(p != NULL) vs. if(!p) > >I offer one of my favourite obfuscated one liners: > > if (strcmp(s1, s2) == 0) vs. if (!strcmp(s1, s2)) > > >In general, I try to use the yardstick, >"if it seems like a neat trick, don't do it!" Speaking as one who distinctly falls into the "not minimizing the total character count" school, I fear I must disagree with these specific examples. When I see if (!p) I read it as if p is not valid then ... The (!p) syntax tells me that p is among the class of items that may be treated as boolean (under the C language conventions) and that we are testing whether it is false. This is not a matter of "saving characters"; it is a matter of classification. When I see if (p != NULL) it tells me two rather different things. First of all it tells me that p is an item for which there are one or more coded values, among which is NULL, and that for all cases where p is not NULL, there is some action to be taken. Secondly it tells me that the file that the statement is in includes stdio.h (or that the author of the code is a dweeb.) And that should tell me that the code in this file needs stdio.h, FOR I DO NOT CONSIDER IT GOOD PROGRAMMING PRACTICE TO INCLUDE INCLUDE FILES WHICH ARE NOT USED. This is not an issue of compactness; rather it is an issue of clarity of format. Different people, different strokes, and all that. My vote is for simple clear code, generously commented. But clarity is relative -- if you understand the principles of a particular algorithm or language or the standard conventions of a large program then a piece of code may be quite clear to you and cryptic to someone without that understanding. And I do not really see that one can claim to understand C and also claim that 'if (!p)' is obscure. -- Richard Harter, SMDS Inc. [Disclaimers not permitted by company policy.]
guy%gorodish@Sun.COM (Guy Harris) (06/28/87)
> When I see > > if (!p) > > I read it as > > if p is not valid then ... > > The (!p) syntax tells me that p is among the class of items that may be > treated as boolean (under the C language conventions) and that we are > testing whether it is false. This is not a matter of "saving characters"; > it is a matter of classification. But what does it mean to say that a pointer is "false"? Pointers themselves really aren't Boolean; there is a boolean predicate *on* a pointer, namely the "is this pointer null" predicate. You could view the construct "p", used in a context that requires a Boolean expression, as really meaning "is_non_null(p)", and "!p" as meaning "!is_non_null(p)", or "is_null(p)". > When I see > > if (p != NULL) > > it tells me two rather different things. First of all it tells me that > p is an item for which there are one or more coded values, among which > is NULL, and that for all cases where p is not NULL, there is some action > to be taken. All of which happen to be the case for pointers. Are you arguing that "!p" is somehow better than "p != NULL"? This is a matter of taste; if you do not view "!p" as shorthand for "is_null(p)", then "p == NULL" makes more sense as a way of writing "is_null(p)", and many good programmers do not view "!p" as such a shorthand. > Secondly it tells me that the file that the statement is in > includes stdio.h (or that the author of the code is a dweeb.) And that > should tell me that the code in this file needs stdio.h, FOR I DO NOT > CONSIDER IT GOOD PROGRAMMING PRACTICE TO INCLUDE INCLUDE FILES WHICH ARE > NOT USED. OK, so either: 1) stick #define NULL 0 at the front of all modules not including <stdio.h> 2) say "if (p == 0)" instead of "if (p == NULL)" 3) in an ANSI C implementation, stick #include <stddef.h> at the front of your module; you *are* using at least one of the items it defines, namely NULL. Also, you didn't address the issue of if (!strcmp(str1, str2)) which I find less defensible. "strcmp" is not really boolean; it's a function from the set of strings to the set of "int"s, such that there are three predicates on the result with the following properties: strcmp(str1, str2) == 0 iff str1 == str2 strcmp(str1, str2) > 0 iff str1 > str2 strcmp(str1, str2) < 0 iff str1 < str2 (where something like "str1 == str2" refers to equality in the sense of string equality, *not* in the sense of pointer equality) and, as such, there are several predicates to be applied to the result of "strcmp" that model various predicates that can't be applied directly to strings in C. Given that, "!strcmp(str1, str2)" is "meaningful" in the sense that the semantics of C give it a meaning; however, it isn't "meaningful" in the sense that it very clearly suggests what you're testing. "!strcmp(str1, str2)" means that the two strings are equal, but someone not reading the expression carefully and just seeing the "!" might see it as testing whether they were *un*equal. If, however, there were a macro "streq", defined as #define streq(str1, str2) (strcmp(str1, str2) == 0) (we ignore the efficiency issue here, and don't stick in the recently-much-discussed optimization of comparing the first two characters) one could write "streq(str1, str2)" and it would be more clear that it was testing whether the strings were equal or not. Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
g-rh@cca.CCA.COM (Richard Harter) (06/28/87)
RH: Richard Harter
GH: Guy Harris
In article <22250@sun.uucp> guy%gorodish@Sun.COM (Guy Harris) writes:
RH: When I see
RH:
RH: if (!p)
RH:
RH: I read it as
RH:
RH: if p is not valid then ...
RH:
RH: The (!p) syntax tells me that p is among the class of items that may be
RH: treated as boolean (under the C language conventions) and that we are
RH: testing whether it is false. This is not a matter of "saving characters";
RH: it is a matter of classification.
GH: But what does it mean to say that a pointer is "false"? Pointers
GH: themselves really aren't Boolean; there is a boolean predicate *on* a
GH: pointer, namely the "is this pointer null" predicate. You could view
GH: the construct "p", used in a context that requires a Boolean
GH: expression, as really meaning "is_non_null(p)", and "!p" as meaning
GH: "!is_non_null(p)", or "is_null(p)".
Well now, C doesn't really have boolean types, but it does have
a convention that anything which is not zero tests true and any thing
which is zero tests false, and the convention that a null pointer (which
we all know is 0x6000000 :-)) behaves on assignment and testing as zero.
So the expression "!p" in "if (!p)" is "null pointer", i.e. not a potent-
ionally legal pointer. Since that is what I am asking, I see the construct
as saying what I mean.
RH: When I see
RH:
RH: if (p != NULL)
RH:
RH: it tells me two rather different things. First of all it tells me that
RH: p is an item for which there are one or more coded values, among which
RH: is NULL, and that for all cases where p is not NULL, there is some action
RH: to be taken.
GH: All of which happen to be the case for pointers. Are you arguing
GH: that "!p" is somehow better than "p != NULL"? This is a matter of
GH: taste; if you do not view "!p" as shorthand for "is_null(p)", then "p
GH: == NULL" makes more sense as a way of writing "is_null(p)", and many
GH: good programmers do not view "!p" as such a shorthand.
Well, as you say, it is a matter of taste. Many good programmers
use "p != NULL" and many equally good programmers use "!p". However let
us define FALSE as zero explictly and ask which is more reasonable to
say "if (p == FALSE)" or "if (!p)", or, in English,
if p is false then ... vs
if not p then
This is truly a matter of taste; many people would find the first form
to be clearer and more comprehensible. Many other people would find the
the second form equally clear or clearer, and the first form to be redundant.
I suspect that people who have a background in formal logic will prefer the
second form. From my viewpoint, the form "!p" asserts that "p" is in the
large class of things which takes the values "true" (where true is any
non null) and "false" (null) and that I am testing on whether it is false
or not. Note that under this viewpoint I don't care what the specific
values for "true" and "false" are -- I only care that the semantics
of the language provide for a "true" and a "false".
Now the chap who prefers "p == FALSE" doesn't see it that way.
He sees it as a test on whether p has a specified value. This, too,
is a perfectly legitimate approach. However...
I take exception to those who, only understanding their own
viewpoint and approach, treat any other approach based on some other
viewpoint as being bad code. The person to whom I originally responded
supposed that "if (!p)" was a sloppy abomination used to save characters.
This is an intensely parochial view.
RH: Secondly it tells me that the file that the statement is in
RH: includes stdio.h (or that the author of the code is a dweeb.) And that
RH: should tell me that the code in this file needs stdio.h, FOR I DO NOT
RH: CONSIDER IT GOOD PROGRAMMING PRACTICE TO INCLUDE INCLUDE FILES WHICH ARE
RH: NOT USED.
GH: OK, so either:
GH:
GH: 1) stick
GH:
GH: #define NULL 0
GH:
GH: at the front of all modules not including <stdio.h>
GH:
GH: 2) say "if (p == 0)" instead of "if (p == NULL)"
GH:
GH: 3) in an ANSI C implementation, stick
GH:
GH: #include <stddef.h>
GH:
GH: at the front of your module; you *are* using at least one
GH: of the items it defines, namely NULL.
Sorry Guy, this one I cannot buy. You third alternative is not
acceptable for those of us who are writing code which runs on a variety
of implementations. The first is a kludge. The second is perfectly
acceptable. In point of fact we actually defined lower case null as 0
in the stddef just to avoid conflicts with stdio.h. We also have FALSE
defined as 0 (obviously). So we have lots of choices. However we don't
use NULL unless it is specifically referring to a null file pointer.
GH: Also, you didn't address the issue of
GH:
GH: if (!strcmp(str1, str2))
GH:
GH: which I find less defensible. "strcmp" is not really boolean; it's a
GH: function from the set of strings to the set of "int"s, such that
GH: there are three predicates on the result with the following
GH: properties:
GH:
GH: strcmp(str1, str2) == 0 iff str1 == str2
GH: strcmp(str1, str2) > 0 iff str1 > str2
GH: strcmp(str1, str2) < 0 iff str1 < str2
GH:
GH: (where something like "str1 == str2" refers to equality in the sense
GH: of string equality, *not* in the sense of pointer equality) and, as
GH: such, there are several predicates to be applied to the result of
GH: "strcmp" that model various predicates that can't be applied directly
GH: to strings in C. Given that, "!strcmp(str1, str2)" is "meaningful"
GH: in the sense that the semantics of C give it a meaning; however, it isn't
GH: "meaningful" in the sense that it very clearly suggests what you're
GH: testing. "!strcmp(str1, str2)" means that the two strings are equal,
GH: but someone not reading the expression carefully and just seeing the
Here I have to agree -- strcmp returns, according to the semantics
of C, a value of true if the strings are unequal and a value of false if
they are equal. Accordingly, strcmp is not really in the category of
functions which match the semantics of C. In this case, !strcmp is a
trick, which is an entirely different matter. We seldom use the string
library [portability, you know -- we avoid library functions, other than
those we absolutely have to use] so I have never dealt with this particular
issue.
--
Richard Harter, SMDS Inc. [Disclaimers not permitted by company policy.]
[I set company policy.]
gwyn@brl-smoke.ARPA (Doug Gwyn ) (06/29/87)
In this silly debate, people have been making noises that sound as if they thought !p and p!=NULL were equivalent. They of course have opposite meaning, which perhaps helps make the point that one should "say what one means" rather than trying to take clever shortcuts.
ron@topaz.rutgers.edu.UUCP (06/29/87)
I have always wondered why people think NULL is more mnemonic than 0. When I read them as exactly the same (and fortunately in C, they are defined to be). I also wonder about people who define TRUE to be any thing, since it leads to things like if( bool == TRUE ) which is different than if(!bool) Generally, I use !p when I'm dealing with things that are supposed boolean values like if(!isdigit(c)) and comparison to zero for things that are supposed to be returning a value if( malloc(100) == 0 ) MY PET PEEVES: 1. Comparing error returns from UNIX syscalls to be less than zero. UNIX system calls that return ints, are usually defined to return -1 on error. It drives me crazy to see code test for less than zero. It doesn't say returns negative value on error, it says -1. 2. Needless use of the comma operator and parenthesis to demonstrate manhood to the obliteration of code readability, e.g. if((fd=open("foo",1)<0) SCREW this, too difficult, how about writing the code to indicate what is going on: fd = open("foo", 1); if(fd == -1) -Ron
jas@rtech.UUCP (Jim Shankland) (06/29/87)
In article <6034@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes: >In this silly debate, people have been making noises that sound as if >they thought !p and p!=NULL were equivalent. They of course have >opposite meaning, which perhaps helps make the point that one should >"say what one means" rather than trying to take clever shortcuts. Good point. Another example: I frequently see code written on a VAX that incorrectly says "if (*p)" instead of "if (p)" or "if (p != NULL)". Granted, inexperienced C programmers are more likely to make this mistake (then again, staying on the VAX, they never learn better); I still suggest that it is less error-prone to use NULL for zero-valued pointers, '\0' for the zero-valued character, and "if (e)" only if e is CONCEPTUALLY boolean (i.e., it is a variable declared as the typedef 'bool', or its main operator is a relop, or....). This is, indeed, a silly debate, and I'm sure we will never come to consensus; but years of moving other people's C code to dozens of different machines has absolutely convinced me that being able to say: if (integer-or-pointer-valued-expr) is an error-prone misfeature, and that we'd all be better off programming as though it didn't exist. -- Jim Shankland ..!ihnp4!cpsc6a!\ rtech!jas ..!ucbvax!mtxinu!/
mlandau@Diamond.BBN.COM (Matt Landau) (06/29/87)
In comp.lang.c (<13008@topaz.rutgers.edu>), ron@topaz.rutgers.edu writes: >and comparison to zero for things that are supposed to be returning >a value > if( malloc(100) == 0 ) I tend to use NULL in this case because it has the connotation of a zero valued *pointer*. Thus, seeing NULL instead of 0 clues the reader in to the fact that it's a pointer value that is being tested instead of an int or a long (in which case I'd want to see the zero written as 0L anyway). >MY PET PEEVES: > >1. Comparing error returns from UNIX syscalls to be less than zero. > UNIX system calls that return ints, are usually defined to return > -1 on error. It drives me crazy to see code test for less than > zero. It doesn't say returns negative value on error, it says > -1. It's an efficiency hack of sorts. Comparison of a value to zero to check for less than zero values is MUCH cheaper on most architectures than comparison to an explicit -1 (which may require saving, overwriting, and reloading registers). Granted, on a 68000 or a VAX, the difference is probably too small to notice. But believe me, a few such efficiency hacks can make a BIG difference on a slow, stupid 8088! (Yes, I've profiled and benchmarked code on little machines when I used to do that sort of thing professionally, and verified that even little tweaks like this make an important difference in a frequently-executed loop.) >2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) > > SCREW this, too difficult, how about writing the code to indicate > what is going on: > > fd = open("foo", 1); > if(fd == -1) As with any programming language, it's all a matter of being comfortable with the idioms. Anyone who programs in C for any length of time, or who has read K&R carefully, should not be mystified by seeing if ((fp = fopen(foo, "r")) == NULL) Granted, it's easier for novices or occasional programmers to read the more verbose (pascaloid?) forms, but a language isn't designed only for the novice. When I'm doing serious work on large chunks of code, what's important is expressing what's going on concisely, but clearly to anyone who knows *the standard C idioms*. I'd rather fit that extra couple of lines of code on the screen or page than not. Besides, the single statement version may actually be EASIER to read: to me, it says "open this file and check to see if the fopen call succeeded", i.e., it's conceptually a single atomic operation, and should be written so as to indicate that fact. That's how I define "writing the code to indicate what's going on." On the other hand, this is all aesthetics/religion, and everyone is certainly entitled to his or her opinion. -- Matt Landau "Wage Peace" mlandau@bbn.com
edw@ius2.cs.cmu.edu (Eddie Wyatt) (06/29/87)
In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) writes: > > MY PET PEEVES: > > 1. Comparing error returns from UNIX syscalls to be less than zero. > UNIX system calls that return ints, are usually defined to return > -1 on error. It drives me crazy to see code test for less than > zero. It doesn't say returns negative value on error, it says > -1. > > 2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) > > SCREW this, too difficult, how about writing the code to indicate > what is going on: > > fd = open("foo", 1); > if(fd == -1) > > -Ron > > Comment 1. Its all a question of taste. Comment 2. Though the code may be logically equivalent, the assemble actually generated differs. Example for Suns 3.2 cc: Consider the program main() { int x,y; if (x=y); } Running cc -S over it yields : LL0: .data .text |#PROC# 04 .globl _main _main: |#PROLOGUE# 0 link a6,#0 addl #-LF12,sp moveml #LS12,sp@ |#PROLOGUE# 1 movl a6@(-0x8),a6@(-0x4) jeq L14 L14: LE12: unlk a6 rts LF12 = 8 LS12 = 0x0 LFF12 = 8 LSS12 = 0x0 LP12 = 0x8 .data Consider the program main() { int x,y; x = y; if (x); } Running cc -S over it yields : LL0: .data .text |#PROC# 04 .globl _main _main: |#PROLOGUE# 0 link a6,#0 addl #-LF12,sp moveml #LS12,sp@ |#PROLOGUE# 1 movl a6@(-0x8),a6@(-0x4) tstl a6@(-0x4) ; NOTE the extra tstl jeq L14 L14: LE12: unlk a6 rts LF12 = 8 LS12 = 0x0 LFF12 = 8 LSS12 = 0x0 LP12 = 0x8 .data Note the extra test instruction. So the two methods are not totally equivalent. I prefer "if ((fd = open("foo",O_WRONLY)) < 0)". Major difference being, I parathesize the operation. Just a matter of taste, that's all. Also another note, any good (even bad) optimizing compiler should reckonize that the tstl is over kill and remove it. -- Eddie Wyatt e-mail: edw@ius2.cs.cmu.edu terrorist, cryptography, DES, drugs, cipher, secret, decode, NSA, CIA, NRO.
ehrhart@aai8..istc.sri.com (Tim Ehrhart) (06/29/87)
In article <13008@topaz.rutgers.edu> ron@topaz.rutgers.edu (Ron Natalie) writes: >MY PET PEEVES: > >1. Comparing error returns from UNIX syscalls to be less than zero. > UNIX system calls that return ints, are usually defined to return > -1 on error. It drives me crazy to see code test for less than > zero. It doesn't say returns negative value on error, it says > -1. I'd guess some folks do that because they know that most of today's current microprocessors have more than enough bits in their status register to be able to know if a value is negative or not. This saves the CPU from actually having to do the compare of two values, then looking at it's status register bits to determine the result. What this really means in terms of microprocessor instructions is basically a single operand instruction versus two double operand instructions. For example on a 680x0, here's the assembler code: if ( x < 0) case: tstl d0 | test on value in d0 if ( x == -1) case: moveq #-1,d7 | load -1 into register d7 (if == -1) cmpl d7,d0 | compare d0 with d7 I know it's only a few assembler instructions and a few microseconds, but if I can get small optimizations like this at no cost I'll take them everytime. Using clearly stated '#defines' is place of the magic numbers 0 and -1 is my way of not compromising clarity and readability. >2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > if((fd=open("foo",1)<0) > Once again to save instructions. The function return value is usually already in d0, and it needs only check it's status bits for negative, etc.. as opposed to loading -1 into another register, then comparing the two register values. Sorry for getting so low level, but I think sometimes that is where the answers or the motivation for things lie. And no, I don't think "coding" for these hardware features are non-portable or cutesy. Just getting the job done with littlest work possible without compromising read- ability or maintainability. I can assure these two issue are very important to me also. Tim Ehrhart SRI International Menlo Park, CA 94025 415-859-5842
edw@ius2.cs.cmu.edu (Eddie Wyatt) (06/29/87)
Please excuse the terrible bad spelling of recognize - I was dozing off again. -- Eddie Wyatt e-mail: edw@ius2.cs.cmu.edu terrorist, cryptography, DES, drugs, cipher, secret, decode, NSA, CIA, NRO.
ken@argus.UUCP (Kenneth Ng) (06/30/87)
In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) writes: > I have always wondered why people think NULL is more mnemonic than 0. > -Ron It's not mnemonic, on some machines its just wrong. NULL is ***NOT*** defined as zero on all machines. Therefore the software writen with that assumption will not work on such a machine. More than likely the machine will be blamed even though the writer of the software is to blame. > if((fd=open("foo",1)<0) [edit] > fd = open("foo", 1); > if(fd == -1) Almost agreed: but if a negative return code other than -1 is returned the code doesn't react the same. ... This signature was put in in a way to bypass the ... bogus artificial line limit on the .signature file. ... Also, by its length it adds fodder to help avoid having ... my followups being bounced due to the restriction on ... followup articles. Kenneth Ng: Post office: NJIT - CCCC, Newark New Jersey 07102 uucp !ihnp4!allegra!bellcore!argus!ken *** NOT ken@bellcore.uucp *** bitnet(prefered) ken@orion.bitnet
ken@argus.UUCP (Kenneth Ng) (06/30/87)
In article <10509@sri-spam.istc.sri.com>, ehrhart@aai8..istc.sri.com (Tim Ehrhart) writes: > >2. Needless use of the comma operator and parenthesis to demonstrate > > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) > Once again to save instructions. > Tim Ehrhart SRI International Menlo Park, CA 94025 415-859-5842 How about get a better compiler? Or kicking the buts of the compiler writers and get them to write better compilers? ... This signature was put in in a way to bypass the ... bogus artificial line limit on the .signature file. ... Also, by its length it adds fodder to help avoid having ... my followups being bounced due to the restriction on ... followup articles. Kenneth Ng: Post office: NJIT - CCCC, Newark New Jersey 07102 uucp !ihnp4!allegra!bellcore!argus!ken *** NOT ken@bellcore.uucp *** bitnet(prefered) ken@orion.bitnet
stevesu@copper.TEK.COM (Steve Summit) (06/30/87)
It has been pointed out that my original posting had a blatant error in it: (!p) and (p != NULL) are not at all equivalent, but in fact are exactly opposite. I was at first acutely embarrassed, but I am now flatly amazed that the statements have been argued so hotly, by some of the best minds on the network, without noting this crucial fact. The fact that this error is so easy to make (I spent several hours on the original posting, and I read through it countless times, and I still missed it) ends up illustrating the point very well: when coding, say what you _m_e_a_n. Since I started this, I might as well make my thinking process clear: we have a pointer variable, and it either holds or does not hold a single "out of band" nil value. The closest transliterations of those English statements into C are if(p == NULL) and if(p != NULL) (assuming a proper #definition of NULL). In my opinion, the _m_o_s_t _i_m_p_o_r_t_a_n_t _t_h_i_n_g is to keep that transliteration step simple. Everything else (compilation time, efficiency, source code character count) is secondary. The biggest problem in Software Engineering is not code size or efficiency but correctness: a program is supposed to do what you want it to do, but rarely does. The more I can make my C code "read" like an English description of what the code is supposed to do, the fewer transliteration errors I will make, and the easier my code will be for me, and others, to read. While I am back up on the soapbox, I will respond to one other point: > I take exception to those who, only understanding their own > viewpoint and approach, treat any other approach based on some other > viewpoint as being bad code. The person to whom I originally responded > supposed that "if (!p)" was a sloppy abomination used to save characters. > This is an intensely parochial view. That was only part of my supposition, and it may be parochial, but it's no more parochial than the supposition that "C is for experts" is elitist. "if(p == NULL)" is not only easier for me to understand, but I suspect that it is also easier for people who are not as experienced with C as I am. (Arguments of the form "but anyone who considers himself a C programmer _s_h_o_u_l_d understand" ignored. Face it, there are less experienced C programmers in the world, and one of them will have to maintain your programs some day. Why not make their job a bit easier?) > And I do not really see that one can claim to understand > C and also claim that 'if (!p)' is obscure. Apparently you do not understand me any better than you think I understand you. Steve Summit stevesu@copper.tek.com "I meant what I said and I said what I meant. An elephant's honest, one hundred percent." -- Dr. Suess, "Horton Hears a Who!"
edw@ius2.cs.cmu.edu (Eddie Wyatt) (06/30/87)
In article <926@argus.UUCP>, ken@argus.UUCP (Kenneth Ng) writes: > In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) writes: > > I have always wondered why people think NULL is more mnemonic than 0. > > -Ron > > It's not mnemonic, on some machines its just wrong. NULL is ***NOT*** > defined as zero on all machines. Therefore the software writen with > that assumption will not work on such a machine. More than likely > the machine will be blamed even though the writer of the software is > to blame. Yes, its a software problem, but its a problem with that C compiler. Sorry, but the C compiler is suppose to treat 0 as a special token in this case. Quote from K&R pg. 192 However, it is guaranteed that assignment of the constant 0 to a pointer will produce a null pointer distingushable from a pointer to any object. The concept is extended to comparison too. But as a save guard against implementations of C that don't adhere to this rule, one should use NULL. So to answer Ron's question, you use NULL instead of 0 because there are those implements of C (which are wrongly implemented) that don't treat 0 the way they should. > > > if((fd=open("foo",1)<0) > [edit] > > fd = open("foo", 1); > > if(fd == -1) > > Almost agreed: but if a negative return code other than -1 is returned > the code doesn't react the same. > I can think of no Unix system call that doesn't return -1 on error. So I would say that it's a pretty good bet that "if (call(...) < 0)" and "if (call(...) == -1)" will act the same in all cases. Though, one should always consult the man pages for return values if in doubt. > > Kenneth Ng: Post office: NJIT - CCCC, Newark New Jersey 07102 > uucp !ihnp4!allegra!bellcore!argus!ken *** NOT ken@bellcore.uucp *** > bitnet(prefered) ken@orion.bitnet -- Eddie Wyatt e-mail: edw@ius2.cs.cmu.edu terrorist, cryptography, DES, drugs, cipher, secret, decode, NSA, CIA, NRO.
ron@topaz.rutgers.edu (Ron Natalie) (06/30/87)
> It's not mnemonic, on some machines its just wrong. NULL is ***NOT*** > defined as zero on all machines. Come on. We've been through this a hundred times. Any C compiler that has NULL set to anything other than plain 0 is wrong. It's in the language spec. Pointers must standup to comparison to the constant 0. If NULL is ever set to anything other than 0, it will break far more things. -Ron
guy%gorodish@Sun.COM (Guy Harris) (06/30/87)
> >2. Needless use of the comma operator and parenthesis to demonstrate > > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) > > > > Once again to save instructions. The function return value is usually > already in d0, and it needs only check it's status bits for negative, > etc.. as opposed to loading -1 into another register, then comparing > the two register values. The "< 0" vs. "== -1" may make a difference in the generated code, but if the if ((fd = open("foo", 1)) < 0) vs. fd = open("foo", 1); if (fd < 0) makes a difference, you don't have a good compiler. Our compiler gives the exact same code for if ((fd = open("foo", 1)) < 0) die(); and fd = open("foo", 1); if (fd < 0) die(); namely, something similar to: pea 1 pea "foo" jbsr _open addqw #8,sp movl d0,fd jge 1f jbsr _die 1: Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
dant@tekla.TEK.COM (Dan Tilque;1893;92-789;LP=A;608C) (06/30/87)
Ron Natalie writes: >I have always wondered why people think NULL is more mnemonic than 0. NULL is more mneumonic than 0 because it's easy to confuse 0 (zero) with the letter O. On many printers and some terminals it's impossible to tell them apart without close comparison with a known zero. Since it's bad form to use a variable named O, and the compile will give a warning if you use an undeclared variable, this may not be a problem. However, I suspect that some of the early compilers did not give a warning and just defaulted the letter O to an int. I know this is (was?) a common problem in FORTRAN. > I also wonder about people who define TRUE to be any >thing, since it leads to things like > if( bool == TRUE ) >which is different than > if(!bool) I thought that TRUE and FALSE should be: #define FALSE 0 #define TRUE !FALSE With these #defines the above two statements are equivalent. > >Generally, I use !p when I'm dealing with things that are supposed >boolean values like > > if(!isdigit(c)) It's often easy to miss a single character (especially one that doesn't stand out like the "!") when quickly scanning code. if (isdigit(c) == TRUE) will compile to the same object code on almost every compiler and is easier to grasp immediately. The fact that it's positive logic also makes it easier. --- Dan Tilque dant@tekla.tek.com
mpl@sfsup.UUCP (06/30/87)
In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu.UUCP writes: > I have always wondered why people think NULL is more mnemonic than 0. > When I read them as exactly the same (and fortunately in C, they are > defined to be). I also wonder about people who define TRUE to be any > thing, since it leads to things like > if( bool == TRUE ) > which is different than > if(!bool) I define TRUE to be 1, so I can say things like if (blah) { do some pocessing return TRUE; } return FALSE; I also think NULL *is* more mnemonic than 0, since when I see if (p == NULL) /* which I always use rather than if (!p) */ it is clear that I am doing a *pointer* test rather than any other type. > Generally, I use !p when I'm dealing with things that are supposed > boolean values like > > if(!isdigit(c)) right on! > and comparison to zero for things that are supposed to be returning > a value > if( malloc(100) == 0 ) also right on, but I'd prefer to use NULL for malloc. I insist on if (strcmp(a, b) == 0) however, since strcmp returns an integer, *not* a boolean. > MY PET PEEVES: > > 1. Comparing error returns from UNIX syscalls to be less than zero. > UNIX system calls that return ints, are usually defined to return > -1 on error. It drives me crazy to see code test for less than > zero. It doesn't say returns negative value on error, it says > -1. If you read "The Elements of Programming Style" by Kernighan and Plauger (?) (which you may or may not agree with), the authors suggest that a test for < 0 is better than a test for -1, since a coding error could possibly cause a function to return a value that is out of the legal range (> 0) but is not the intended error value (-1). Not only that, but on many machines, a test for < 0 may be more efficient than a test against a specific value. I know, this s just fluff, but I think it makes a program more readable to test for out-of-range conditions rather than a specific value: n = foo(bar); if (n != -1) a = goo[n]; /* oh, I guess goo[-2] is legal, or am I *that sure* foo could *never* return -2? */ > 2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) > > SCREW this, too difficult, how about writing the code to indicate > what is going on: > > fd = open("foo", 1); > if(fd == -1) > -Ron Oh, but this is one of the things that makes C beautiful. Granted, the comma operator should rarely be used outside of #defines (where it is useful to get some things to work). However, I find if ((fd = open("foo", 1)) < 0) *more* readable than fd = open("foo", 1); if(fd == -1) since what I want to test is the return of open, *not* fd. The "fd =" is only there so I can capture the value to use it later. Mike
gwyn@brl-smoke.UUCP (06/30/87)
In article <926@argus.UUCP> ken@argus.UUCP (Kenneth Ng) writes: >NULL is ***NOT*** defined as zero on all machines. I suspect readers of this newsgroup are sick of this by now, but it is IMPORTANT to understand that: (a) NULL is not defined by a machine; it is defined in <stdio.h> and perhaps other places for programmer convenience (b) #define NULL 0 is ALWAYS a CORRECT definition; this has nothing to do with how null pointers are represented in a C implementation but is required by the C language definition (c) NULL is not appropriate for passing as a function parameter unless it is appropriately cast
karl@haddock.UUCP (Karl Heuer) (06/30/87)
In article <13008@topaz.rutgers.edu> ron@topaz.rutgers.edu (Ron Natalie) writes: >I have always wondered why people think NULL is more mnemonic than 0. >When I read them as exactly the same (and fortunately in C, they are >defined to be). When used properly, NULL refers only to a null pointer constant. Thus, I can read "if (x == NULL)" and know without further context that x is a pointer. (The constant 0 with an explicit (redundant) cast conveys even more information; I use that notation too.) >I also wonder about people who define TRUE to be any thing, since it leads to >things like "if( bool == TRUE )" which is different than "if(!bool)" There is that danger. However, the reason *I* define TRUE (actually I prefer YES, as in K&R) is for assignment ("flag=YES" instead of "flag=1"), not comparison. See above paragraph for reason. >Generally, I use !p when I'm dealing with things that are supposed >boolean values like "if(!isdigit(c))" Good idea. (Btw, note that some implementations of isdigit do not return 1 for success, so "if (isdigit(c) == TRUE)" is bad news.) >MY PET PEEVES: ... >2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > if((fd=open("foo",1)<0) > SCREW this, too difficult, how about writing the code to indicate > what is going on: > fd = open("foo", 1); > if(fd == -1) The main advantage of this idiom is for "while" statements. The usual example is "while ((c = getchar()) != EOF) ...", which cannot be written cleanly without the embedded assignment. The use in "if" statements often permits one to collapse nested ifs, which can *improve* code readability. Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint
chris@mimsy.UUCP (Chris Torek) (07/01/87)
In article <13008@topaz.rutgers.edu> ron@topaz.rutgers.edu (Ron Natalie) says he hates code that: >[compares] error returns from UNIX syscalls to be less than zero. >UNIX system calls that return ints, are usually defined to return >-1 on error. It drives me crazy to see code test for less than >zero. It doesn't say returns negative value on error, it says -1. % man 2 open ... Upon successful completion a non-negative integer termed a file descriptor is returned. ... This is from the 4.3BSD manuals. -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690) Domain: chris@mimsy.umd.edu Path: seismo!mimsy!chris
ddl@husc6.UUCP (Dan Lanciani) (07/01/87)
In article <1176@copper.TEK.COM>, stevesu@copper.TEK.COM (Steve Summit) writes: | It has been pointed out that my original posting had a blatant | error in it: (!p) and (p != NULL) are not at all equivalent, but | in fact are exactly opposite. I was at first acutely embarrassed, | but I am now flatly amazed that the statements have been argued | so hotly, by some of the best minds on the network, without | noting this crucial fact. I sincerely doubt that the "best minds on the network" are the ones doing the arguing. Nevertheless, I noted this "crucial fact" and hence my question as to whether you had really mastered the idiom. I guess I was too subtle and you missed the point. | The fact that this error is so easy to make (I spent several | hours on the original posting, and I read through it countless | times, and I still missed it) ends up illustrating the point very | well: when coding, say what you mean. I really hope you mean the fact that this error is so easy for YOU to make illustrates the point that YOU need to be very careful when coding. The error was immediately obvious to me and to the only other person I mentioned it to. When I code "if(!p)" I say exactly what I mean and I mean what I say. Your reasoning is about as good as my saying, "Since I can't fly an airplane, it is clearly impossible to do so." | Since I started this, I might as well make my thinking process | clear: we have a pointer variable, and it either holds or does | not hold a single "out of band" nil value. The closest | transliterations of those English statements into C are | | if(p == NULL) | and | if(p != NULL) | | (assuming a proper #definition of NULL). I would claim that the closest transliterations are "if(!p)" and "if(p)," if only because they DON'T assume a proper #define of ANYTHING. | In my opinion, the most important thing is to keep that | transliteration step simple. Again, C has made that step very simple for you by providing the "if(p)" construct. Of course, this is my opinion. | Everything else (compilation time, | efficiency, source code character count) is secondary. Other than the fact that I happen to find less cluttered code easier to read, the issue of character count is a joke. Compilation time is a programmer resource and can often be adjusted... But efficiency is not always a secondary issue. In fact, it is RARELY a secondary issue. Sometimes efficiency makes the difference between an application's working or failing. More often however, this attitude encourages the production of needlessly slow programs with which users must suffer for years. | The | biggest problem in Software Engineering is not code size or | efficiency but correctness: a program is supposed to do what you | want it to do, but rarely does. The more I can make my C code | "read" like an English description of what the code is supposed to | do, the fewer transliteration errors I will make, and the easier | my code will be for me, and others, to read. Maybe you should try COBOL. It was designed to read like an English description. C was not. Why not let C read like a C description of the problem? When I program in C I do not transliterate from English because I find C to provide a better mechanism for describing many problems. This is my personal style. You have your own style. I have no trouble reading code written in either style. Can't we leave it at that rather than trying to prove that one style somehow encourages correctness and another style tends to discourage it? Dan Lanciani ddl@harvard.*
jr@amanue.UUCP (Jim Rosenberg) (07/01/87)
In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) writes: > [... other pet peeves ... ] > > 2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) > > SCREW this, too difficult, how about writing the code to indicate > what is going on: > > fd = open("foo", 1); > if(fd == -1) I recall many moons ago whilst browsing K&R and before really learning C that I swore up and down that I would forego such (what I thought at the time to be) unwarranted over-abbreviation as: while ((c = getchar()) != EOF) { . . . It didn't take me long once I was actually using C on a regular basis to realize that forgoing constructs such as the one above is not only ridiculous, a case can be made that it is actually LESS CLEAR stylistically. Yes, it is more difficult to read for novices to C. But consider the alternative: for (;;) { c = getchar(); if (c == EOF) break; . . . I would argue that a strong case can be made that the for loop is actually LESS CLEAR than the while loop. By announcing the for loop as a forever loop, in effect I'm saying that I really don't know what will terminate this loop 'till I find it. But of course I *do* know, as the while loop shows. I.e. the while loop is more clear because it does a better job of "front-loading" the code so the reader can understand what's coming. A true forever loop is a whole different kettle of fish than a loop where you really can predict in advance the condition that will cause it to terminate. It seems to me to be a self-evident principle of coding clarity that the sooner in the code you can state your intentions the better, other things being equal. If you forego using *ANY* assignments in expressions then you must use the forever loop to code this example, which logically speaking isn't really right. Using assignments in expressions can be taken to extremes of course, but it doesn't always hide what's going on -- sometimes it *REVEALS* what is going on. Note that I'm making this point purely on the basis of code readability, regardless of what number of machine cycles the generated code might save. The case you made was for if statements not loops, but once you get used to the idea, judicious use of an assignment in an expression will seem perfectly natural. I think it certainly does to 90% of all C programmers, and it's not a matter of showing off. It's a matter of saying what you mean. -- Jim Rosenberg CIS: 71515,124 decvax!idis! \ WELL: jer allegra! ---- pitt!amanue!jr BIX: jrosenberg seismo!cmcl2!cadre! /
edw@ius2.cs.cmu.edu (Eddie Wyatt) (07/01/87)
In article <221@amanue.UUCP>, jr@amanue.UUCP (Jim Rosenberg) writes: > while ((c = getchar()) != EOF) { > . > . > . > > It didn't take me long once I was actually using C on a regular basis to > realize that forgoing constructs such as the one above is not only ridiculous, > a case can be made that it is actually LESS CLEAR stylistically. Yes, it is > more difficult to read for novices to C. But consider the alternative: ^^^^^^^^^^^^^^^ > > for (;;) { > c = getchar(); > if (c == EOF) > break; > . > . > . [ lots of stuff about how this is unreadable] > -- > Jim Rosenberg > CIS: 71515,124 decvax!idis! \ > WELL: jer allegra! ---- pitt!amanue!jr > BIX: jrosenberg seismo!cmcl2!cadre! / "the alternative"! Come on there are a thousand and one ways to code the semantics of the above loop. For example : c = getchar(); while (c != EOF) { . . . c = getchar(); } Look, no assignments in the conditionals, no hidden gotos (break). For the people that argue the first form is bad, this would probably be the approach they would take. I still prefer the first form though (the assignment inside the conditional). Its just a matter of style to me, not readablity. -- Eddie Wyatt e-mail: edw@ius2.cs.cmu.edu terrorist, cryptography, DES, drugs, cipher, secret, decode, NSA, CIA, NRO.
dg@wrs.UUCP (David Goodenough) (07/01/87)
In article <927@argus.UUCP> ken@argus.UUCP (Kenneth Ng) writes: >In article <10509@sri-spam.istc.sri.com>, ehrhart@aai8..istc.sri.com (Tim Ehrhart) writes: >> >2. Needless use of the comma operator and parenthesis to demonstrate >> > manhood to the obliteration of code readability, e.g. >> > if((fd=open("foo",1)<0) >> > [edit] >> > fd = open("foo", 1); >> > if (fd < 0) but what about the line I use (in one form or another) in maybe 40% of the C programs I write: while ((ch = getchar()) != EOF) (think about it!! :-)) and on the subject of readability one thing that would help above _ALL_ others is the use of white space, not just for indenting, but for separating lexical tokens (where appropriate) in expressions: I find the above while statement 1000 times easier to read than: while((ch=getchar())!=EOF) -- dg@wrs.UUCP - David Goodenough +---+ | +-+-+ +-+-+ | +---+
rice@swatsun (Dan Rice) (07/02/87)
Here's a simple question for you. Suppose I have defined structures containing other structures, i.e., typedef struct { float x, y, z; /* Coordinates of a vector in 3-space */ } vector; typedef struct { vector o; /* Center */ float r; /* Radius */ } sphere; sphere s1, *s2; Now, say I refer to s1.o.x and s2->o.y. Does the compiler convert this into a simple address reference at compile time, or is work performed at runtime? Should I define vector center; center = s2->o; if I plan to use s2->o several times? Thanks for any help. -- - Dan Rice, Swarthmore College, Swarthmore PA 19081 ...!sun!liberty!swatsun!rice ...!seismo!bpa!swatsun!rice
rblieva@cs.vu.nl (Roemer b Lievaart) (07/02/87)
I know I won the "obfuscated C code contest", but I guess I didn't deserve it, when I read these articles! :-)))) No, serious, I just joined in (and will join out soon, vacation), but I can't help thinking I must put it all straight. Probably I missed most of the point of these articles, but I didn't miss the blunders spread around! Flame on! In article <1941@zeus.TEK.COM> dant@tekla.UUCP (Dan Tilque) writes: >Ron Natalie writes: > >> I also wonder about people who define TRUE to be any >>thing, since it leads to things like >> if( bool == TRUE ) >>which is different than >> if(!bool) Of course it's different. If bool can only be 0 or TRUE, "bool == TRUE" is exactly the opposite of " ! bool ", isn't it? Unless TRUE is defined as 0, which is a great idea for the next year's obf. C contest. > >I thought that TRUE and FALSE should be: > >#define FALSE 0 >#define TRUE !FALSE > >With these #defines the above two statements are equivalent. Somebody is being very stupid - the two of you or me... who can tell? >> >>Generally, I use !p when I'm dealing with things that are supposed >>boolean values like >> >> if(!isdigit(c)) > >It's often easy to miss a single character (especially one that doesn't >stand out like the "!") when quickly scanning code. I have no problems with that as I mostly write if ( ! isdigit(c) ) Stands out enough for me. > > if (isdigit(c) == TRUE) > >will compile to the same object code on almost every compiler and is easier >to grasp immediately. The fact that it's positive logic also makes it >easier. > Sigh. I really wouldn't define TRUE as 0. However if (isdigit(c) == 0) is a good idea, I would say. But! To avoid some misunderstandings: isdigit(), isalpha(), etc. do NOT just return FALSE or "TRUE" (1)!!! Watch the next program and its output: ----- #include <ctype.h> #define FALSE 0 #define TRUE (!FALSE) main() { printf("%d %d %d\n", isalpha('a'), isupper('Z'), isdigit('0') ) ; if ( isalpha('a') ) puts("yes") ; else puts("no") ; if ( isalpha('a') == TRUE ) puts("yes") ; else puts("no") ; } ------ output: ------ 2 1 4 yes no -------- Do you get it? isalpha('a') is not true! $%&#?! It's not true, it's 2. This may look sickening to some, but if you think so, learn Pascal. And so I come to another article (too bad rn lets you 'F'ollowup only one article at a time) : In article <whatever> jas@rtech.UUCP (Jim Shankland) writes: > In article <6034@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes: > This is, indeed, a silly debate, and I'm sure we will never come to > consensus; but years of moving other people's C code to dozens of > different machines has absolutely convinced me that being able to say: > > if (integer-or-pointer-valued-expr) > > is an error-prone misfeature, and that we'd all be better off programming > as though it didn't exist. > -- > Jim Shankland I get your point Jim, but I must say it's rather Pascal-minded (or whatever other languages that are boolean-freaking). In C it doesn't always work that perfect, as I illustrated above. So what do you do? You adapt yourself to C (instead of all those people who want to adapt C or the other C-programmers) and you learn to work with things like if ( strlen( name ) ) ... and get used to it, so that it is just as clear for you as is if ( strlen( name ) != 0 ) or even "better" if ( strlen( name ) > 0 ) It is for me now. It can be for you too. Just as you learn that in Pascal if a then is equivalent to if a = true then you'll have to learn that in C if ( a ) is equivalent to if ( a != 0 ) [ new subject: ] However, I must say I'm very against using '0' instead of NULL. I know its the same, but I want to know whether someone is working with integers or pointers. (Yes I know, maybe I have too learn that, too, just as the thing I described above, but I don't think so. Because the example above really can give wrong results, as indicated, and a strict 0 / NULL discrimination only makes your programs clearer, without side-effects.) And so I come to a last point: Our lint complains about: main(){ bar( NULL ) ; } bar ( foo ) char *foo ; { ... } I know that indeed there better should be a complaint in the second case, but it should be like: "the compiler doesn't know that the parameter 0 is a pointer; possible alignment problem" or something like that. Instead lint says: "bar, arg. 1 used inconsistently ..." Which is not true. I didn't use the argument inconsistently, I passed a NULL-pointer which is a correct pointer. The problem is that the compiler doesn't KNOW it's a pointer (it's just the integer 0), as long as I won't put "(char *)" in front of it. Okay, I can get over this. But how about this: char *strings[] = { "one", "two", "three", NULL } ; This won't pass our compiler. Which is dumb, as the compiler knows pointers to characters are coming up, finds a zero, and does not conclude it should be a NULL-pointer. But is does conclude so in: char *onestring = NULL ; Strange, I would say. How do other compilers react to this? Anyway, that's enough for today. It's almost bedtime, let's not dream about null-pointers, let's dream about birds, music, love and all those other things some programmers sometimes forget... :-) (And I may be well one of those addicts!) Let's live, Roemer B. Lievaart, Amsterdam. Disclaimer: The opinions expressed by the VU are not mine.
marc@pismo.cs.ucla.edu (Marc Kriguer) (07/02/87)
In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu.UUCP writes: > MY PET PEEVES: > > 2. Needless use of the comma operator and parenthesis to demonstrate > manhood to the obliteration of code readability, e.g. > > if((fd=open("foo",1)<0) (versus) > fd = open("foo", 1); > if(fd == -1) > -Ron This is an example of the use of the comma operator? I thought the comma operator was something along the likes of: { a = b, c} which sets a to b, then returns the value of c. Isn't the comma in Ron's example just a delimiter between parameters to open() ? _ _ _ Marc Kriguer / \/ \/ \ / / / __ ___ __ BITNET: REMARCK@UCLASSCF / / / / \ / / / ARPA: marc@pic.ucla.edu / / / \__/\_/ \_/\__/ UUCP: {backbones}!ucla-cs!pic.ucla.edu!marc
ron@topaz.rutgers.edu (Ron Natalie) (07/02/87)
> The main advantage of this idiom is for "while" statements. The usual > example is "while ((c = getchar()) != EOF) ...", which cannot be > written cleanly without the embedded assignment. The use in "if" > statements often permits one to collapse nested ifs, which can > *improve* code readability. If "c" is of type "char" this is still not written cleanly. EOF is type int. The assignment into a character causes getchar's int return to be changed to char, invalidating the following comparison. The effect is that characters of value 0xFF will cause erroneous end of file indication. This is a common error in C, but it is fortunate that most text this is used on never contains any characters with the most significant bit set. To do this right you need an extra int temporary value while ((i = getchar()) != EOF) or while( i = getchar(), i != EOF) followed by c = i; -Ron
ron@topaz.rutgers.edu (Ron Natalie) (07/02/87)
More precisely you, I meant to the say the comma operator OR parentheses. The example used parentheses but you could have as easily said: if(fd = open("foo", 0), fd < 0)
cramer@kontron.UUCP (Clayton Cramer) (07/03/87)
> In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) writes: > > I have always wondered why people think NULL is more mnemonic than 0. > > -Ron > > It's not mnemonic, on some machines its just wrong. NULL is ***NOT*** > defined as zero on all machines. Therefore the software writen with > that assumption will not work on such a machine. More than likely > the machine will be blamed even though the writer of the software is > to blame. Or even the same machine. For example, if you are compiling in small model on the 8086 with the Microsoft compiler: #define NULL 0 but in large model: #define NULL 0L Depending on how you are using it, the results may come out the same -- but relying on the compiler to notice that are comparing int and long (in large model) is a very poor practice. Clayton E. Cramer
guy%gorodish@Sun.COM (Guy Harris) (07/03/87)
> The assignment into a character causes getchar's int return > to be changed to char, invalidating the following comparison The > effect is that characters of value 0xFF will cause erroneous end of > file indication. This is a common error in C, but it is fortunate > that most text this is used on never contains any characters with the > most significant bit set. On a machine where "char" is not signed, this won't work even if you run it on text consisting entirely of 7-bit characters. "getchar" will return EOF when it hits end-of-file; EOF (-1) will get converted to '\377' (we assume 8-bit characters here; translate as needed for other character sizes) which compares equal to 255 but not to -1. Thus, you'll never see the end-of-file indication. Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
guy%gorodish@Sun.COM (Guy Harris) (07/03/87)
> It's not mnemonic, on some machines its just wrong. NULL is ***NOT*** > defined as zero on all machines. There does not exist, and will never exist, a machine for which a legitimate C compiler will produce different code for: char *p; if (p == 0) and char *p; if (p == NULL) if NULL is defined as 0 - just 0, nothing else. The same is true for char *p; p = 0; and char *p; p = NULL; (If anybody cares to challenge this statement, I will point out that all such challenges in the past have been refuted. There are passages in K&R and in the ANSI C draft that make it abundantly clear that the compiler must do the right thing both for "if (p == 0)" and "p = 0"; for example, if you have 16-bit "int"s and 32-bit pointers, the compiler must not generate a 16-bit assignment or a 16-bit comparison. If this seems counter-intuitive to anybody, replace "char *p" with "long p", and note that the compiler obviously should not generate 16-bit assignments or comparisons in those cases. A pointer is no different here.) Neither pointer assignment nor pointer comparison, then, requires any decoration to be wrapped around 0 to make them work. The only case where you have to wrap decoration around 0 to make it work is when you are passing null pointers as arguments to procedures. In the general case, there is no magic goop that you can wrap around *all* instances of 0 used as null pointers that will make it work for all those instances. For instance, you might have a word-addressible 16-bit machine, where character pointers require 32 bits. In this case, passing "(char *)0" to a procedure might be done by passing 32 bits of zero, and passing "(int *)0" might be done by passing 16 bits of zero. As such, the only correct way to handle this is to cast all occurrences of 0 or NULL to a pointer of the appropriate type when passing them as procedure arguments. (If you don't, and you have a type-checker like "lint", if it's any good it will complain about code that doesn't properly cast the pointers.) Some vendors offering C implementations on machines where all pointers are the same length, and all null pointers have the same bit pattern, cheat by defining NULL as 0L or (char *)0. This makes the code work - *on that particular machine* - but doesn't make it correct, in the sense that it will work correctly on any correct C implementation. (It also won't silence a good type-checker. In fact, the "NULL is (char *)0" trick would NOT silence complaints from a type-checker if you write code that does the casting properly, whereas leaving NULL as 0 would do so.) Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
guy%gorodish@Sun.COM (Guy Harris) (07/03/87)
> Or even the same machine. For example, if you are compiling in small > model on the 8086 with the Microsoft compiler: > > #define NULL 0 > > but in large model: > > #define NULL 0L > > Depending on how you are using it, the results may come out the same -- > but relying on the compiler to notice that are comparing int and long > (in large model) is a very poor practice. Where are "int"s and "long"s being compared here? Ron Natalie was referring to constructs such as char *p; if (p == NULL) and in this case a *pointer*, which is a totally different animal from an "int" or a "long", is being compared with NULL, which is generally an "int" except on some implementations that cheat and make it a "long". Any correct C compiler will generate a 32-bit comparison in the large model for the code above (or whatever comparison tests *all* the bits in the pointer that are actually used; if, say, 8 of those bits are not considered part of the pointer's value, they need not be compared). Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
rwa@auvax.UUCP (Ross Alexander) (07/03/87)
The following discussion is all relative to #define EOF -1 char c; while ( ( c = getchar() ) != EOF ) { /* do something */ } In article <22635@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: > > The assignment into a character causes getchar's int return > > to be changed to char, invalidating the following comparison> > On a machine where "char" is not signed, this won't work even if you > run it on text consisting entirely of 7-bit characters. "getchar" > will return EOF when it hits end-of-file; EOF (-1) will get converted > to '\377' ... which compares equal to 255 but not to -1. Isn't there a little room here for arguement? Now of course K&R say that an expression is coerced to the type it is being assigned to. This is _a good thing_, since otherwise there's no way to assign to a char (think about the promotion rules :-). But isn't assignment, in the strictest sense, a side effect? Or phrased a little differently, is the comparison being made to the value of c or the value of the getchar() ? So the coercion for the assignment of `int' to `char' in this case is a side effect and shouldn't have an affect on the value of the whole expression `( c = getchar() )'. Interestingly enough, the VAX 4.2bsd cc agrees with me, and produces (code to get value of getchar() ommited, but it's in r0): L9998: cvtlb r0,-1(fp) ; coercion and assignment to c as byte cmpl r0,$-1 ; but comparison is int to int jeql L36 ; break the loop iff same whilst the SUN 4.2 cc feels otherwise, and produces (getchar() value in d0): L2000001: movb d0,a6@(-0x1) ; assignment to c cmpb #-0x1,d0 ; comparison of a byte to a byte jeq L23 ; break loop iff same Now while some might argue that the second interpretation is properly conformant, and the first is brain-damaged (smile, gentle people), I would argue that the first way of doing things is less likely to get the poor programmer (myself, for instance) into trouble. I argue that this is because in this context, the assignment is a side effect, and isn't intended to `narrow' the value of the expression. Just to point out where the logical extension of case two gets us, what is the value of x in this fragment: float x; int i; x = 2.0 + ( i = 3.5 ); I would say 5.5; others might say 5.0, it seems. But if I _wanted_ 5.0, I would expect to write x = 2.0 + (int) ( i = 3.5 ); and I appeal to the principle of least astonishment for justification. ...!ihnp4!alberta!auvax!rwa Ross Alexander @ Athabasca University
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/04/87)
In article <1571@sfsup.UUCP> mpl@sfsup.UUCP writes: >If you read "The Elements of Programming Style" by Kernighan and Plauger (?) >(which you may or may not agree with), the authors suggest that a test for >< 0 is better than a test for -1, since a coding error could possibly cause >a function to return a value that is out of the legal range (> 0) but is not >the intended error value (-1). Some people (Dijkstra and Gries among them) no longer believe that this kind of "conservative" test is preferable to the exact test; they base this on what it takes to prove an implementation of an algorithm to be correct. >Not only that, but on many machines, a test for < 0 may be more >efficient than a test against a specific value. I doubt that the difference is detectable (remember, the code just made a system call!).
spencer@osu-cgrg.UUCP (Steve Spencer) (07/04/87)
In article <1213@carthage.swatsun.UUCP>, rice@swatsun (Dan Rice) writes: > > center = s2->o; > > if I plan to use s2->o several times? Thanks for any help. > -- > - Dan Rice, Swarthmore College, Swarthmore PA 19081 > ...!sun!liberty!swatsun!rice > ...!seismo!bpa!swatsun!rice Yes, I would (and have) used something like what you describe when I have to use the current values in s2->o many times in a function (example: if s2->o was the intersection point of a ray with a sphere in a ray-tracing program and the function in which I was working was a shader. :-)) BUT...... Some C compilers will let you get away with center = s2->o; (i.e.: structure assignments) But some won't. Your best bet for non-flakiness is: center.x = (s2->o).x; center.y = (s2->o).y; center.z = (s2->o).z; Sure, it's three statements instead of one, but (1) You are certain that all three values were assigned correctly. (2) If you had to access (as you speculated) s2->o.x multiple times, you are saving yourself cycles in the long run. Hope I helped... steve -- ...I'm growing older but not up... - Jimmy Buffett Stephen Spencer, Graduate Student The Computer Graphics Research Group The Ohio State University 1501 Neil Avenue, Columbus OH 43210 {decvax,ucbvax}!cbosg!osu-cgrg!spencer (uucp)
ark@alice.UUCP (07/04/87)
In article <262@auvax.UUCP>, rwa@auvax.UUCP writes: > what is the value of x in this fragment: > > float x; > int i; > > x = 2.0 + ( i = 3.5 ); > > I would say 5.5; others might say 5.0, it seems. But if I _wanted_ > 5.0, I would expect to write > > x = 2.0 + (int) ( i = 3.5 ); > > and I appeal to the principle of least astonishment for justification. Ah yes, what should be the value of an assignment? The LHS or the RHS? Both answers have their pitfalls, and C says the correct answer is (a copy of) the LHS. Thus the answer in your example is 5.0. While this produces a surprise in your particular case, consider: double d, a, sqrt(); d = 2; a = sqrt(d); a = sqrt(d=2); Now, don't you want the second and third assignment above to do the same thing? In APL, the value of an assignment is the value of its right-hand side. This mostly works well, but leads to some surprises too: A <- 0 0 0 0 0 B <- A[2 3 4] <- 6 B <- A[2 3 4] The first line sets A to a five-element array, all of whose elements are zero. The second line sets A[2], A[3], and A[4] to 6, and also sets B to 6. The third line sets B to a three- element array whose elements are all 6.
ron@topaz.rutgers.edu (Ron Natalie) (07/04/87)
Microsoft C (if it really requires NULL to be 0L) is broken by all standards that C compilers have ever been designed to. K&R explicitly defined 0 comparisons to pointers to be the rule and no one since has come up with any convincing arguments to the contrary. -Ron
rwhite@nu3b2.UUCP (Robert C. White Jr.) (07/05/87)
In article <262@auvax.UUCP>, rwa@auvax.UUCP (Ross Alexander) writes: } The following discussion is all relative to } } #define EOF -1 } char c; } while ( ( c = getchar() ) != EOF ) { /* do something */ } } } Isn't there a little room here for arguement? Now of course K&R say } that an expression is coerced to the type it is being assigned to. } This is _a good thing_, since otherwise there's no way to assign to a } char (think about the promotion rules :-). But isn't assignment, in } the strictest sense, a side effect? Or phrased a little differently, } is the comparison being made to the value of c or the value of the } getchar() ? So the coercion for the assignment of `int' to `char' in } this case is a side effect and shouldn't have an affect on the value } of the whole expression `( c = getchar() )'. Interestingly enough, } the VAX 4.2bsd cc agrees with me, and produces (code to get value of } getchar() ommited, but it's in r0): } } L9998: } cvtlb r0,-1(fp) ; coercion and assignment to c as byte } cmpl r0,$-1 ; but comparison is int to int } jeql L36 ; break the loop iff same } } whilst the SUN 4.2 cc feels otherwise, and produces (getchar() value in d0): } } L2000001: } movb d0,a6@(-0x1) ; assignment to c } cmpb #-0x1,d0 ; comparison of a byte to a byte } jeq L23 ; break loop iff same } } Now while some might argue that the second interpretation is properly } conformant, and the first is brain-damaged (smile, gentle people), I } would argue that the first way of doing things is less likely to get } the poor programmer (myself, for instance) into trouble. I argue } that this is because in this context, the assignment is a side } effect, and isn't intended to `narrow' the value of the expression. } Just to point out where the logical extension of case two gets us, } what is the value of x in this fragment: } } float x; } int i; } } x = 2.0 + ( i = 3.5 ); } } I would say 5.5; others might say 5.0, it seems. But if I _wanted_ } 5.0, I would expect to write } } x = 2.0 + (int) ( i = 3.5 ); } } and I appeal to the principle of least astonishment for justification. Sir, In my copy of K&R's "The C Programming Language" the last line of section 2.10 titled "Assignment Operators and Expressions" states: "The type of an assignment expression is the type of the left oppernd" In "The C Programmers Handbook" [a Prentice Hall publication sanctioned by bell labs if it really matters] at the top of page #12 just under the title "ASSIGNMENT" is the following: NOTE -- The value of an assignment expression is the value of the left opperand _AFTER_ the assignment. It would seem obvious to me, even without these quotes as backup, that your vax is leading you down the garden path and you are happy to follow. The original questioner also must be questioned. K&R repeatedly and constantly state that in the expression (c = getchar()) c must be type int in order to preserve the EOF condidion because EOF must lie outside the range of any and all possible characters and that any value held by a variable of type char is a valid character. In your first code fragment c should be type int, and any opperations on c will be cast as approprate. In your statement that the assignment is, or should be, a secondary consideration to the value... The parens "(" ")" would say quite the contrary. As all math is done as longs and doubles under "C", at least according to the stndard, the type of an expression must come from somewhare. The precidence rules dont allow the expression without the parens, to return any value but 0 or 1 th the variable c. In your second code fragment the answer is 5.0. You dont need the cast to int because the declaration of i as int does that. The rule of least astonishment involves the comparison of explicit opperations to implicit, stating that the implicit should follow the explicit. That is, if you place every parened fraagment in an atonomus statment the values should remain unchanged. therefore: char c; while ((c = getchar()) != EOF) { /* Valid Opperations */ } Becomes: char c; c = getchar(); while (c != EOF) { /* Valid Opperations */; c = getchar (); } Which you may recognize, in form anyway, from pascal. By definition both these loops must behave exactly the same. By your argument the second fails but the first would opperate properly. With least astonishment in "C" this loop will never terminate, in either form, with c typed as char but it will function correctly with c typed as int becaues, as you know, type char cannot hold EOF but int can. Robert. Disclaimer: My mind is so fragmented by random excursions into a wilderness of abstractions and incipient ideas that the practical purposes of the moment are often submerged in my consciousness and I don't know what I'm doing. [my employers certainly have no idea]
g-rh@cca.CCA.COM (Richard Harter) (07/05/87)
In article <6051@brl-smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes: >In article <1571@sfsup.UUCP> mpl@sfsup.UUCP writes: >Some people (Dijkstra and Gries among them) no longer believe that this >kind of "conservative" test is preferable to the exact test; they base >this on what it takes to prove an implementation of an algorithm to be >correct. Are you sure that this is what they are saying? Although I have strong reserverations about Dijkstra and his theories about programming he is no dummy. The argument, I would suppose, is that a "conservative" test may conceal errors in the code. This is a well taken point. But the cure is to be both "conservative" and "exact". For example, if a function is supposed to return either -1 or a non-negative integer then you should test for -1 special return <-1 invalid return >-1 legitimate return and robust code will make all three tests. To test for <0 is to fuse two different classes of returns. (People who worry about the cost of two tests instead of one in these situations end up with code with mystery bugs in them.) Incidentally, my objection to Dijkstra et. al., is just in this conception of code as an implementation of an algorithm. In my experience, algorithms, once you have a stock of them under your belt are a minor element in software generation. Coding an algorithm is simple (usually -- I grant that there are some hairy ones.) You can use black box techniques and treat them in isolation. The important part in design and implementation is the system specification, structure, and control. Algorithms are just parts on the inventory shelf. -- Richard Harter, SMDS Inc. [Disclaimers not permitted by company policy.] [I set company policy.]
jr@amanue.UUCP (Jim Rosenberg) (07/05/87)
In article <262@auvax.UUCP>, rwa@auvax.UUCP (Ross Alexander) writes: > The following discussion is all relative to > > #define EOF -1 > char c; > while ( ( c = getchar() ) != EOF ) { /* do something */ } > > In article <22635@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: > > > The assignment into a character causes getchar's int return > > > to be changed to char, invalidating the following comparison> > > On a machine where "char" is not signed, this won't work even if you > > run it on text consisting entirely of 7-bit characters. "getchar" > > will return EOF when it hits end-of-file; EOF (-1) will get converted > > to '\377' ... which compares equal to 255 but not to -1. > >Isn't there a little room here for arguement? Now of course K&R say >that an expression is coerced to the type it is being assigned to. >This is _a good thing_, since otherwise there's no way to assign to a >char (think about the promotion rules :-). But isn't assignment, in >the strictest sense, a side effect? Or phrased a little differently, >is the comparison being made to the value of c or the value of the >getchar() ? So the coercion for the assignment of `int' to `char' in >this case is a side effect and shouldn't have an affect on the value >of the whole expression `( c = getchar() )'. Interestingly enough, >the VAX 4.2bsd cc agrees with me ... Gads!!! On this one, when Guy talks you better listen. I earned some cute money once over exactly this point. Someone was trying to port a program from BSD on a VAX to System V on a 3B2. It ran fine on the VAX (don't they always??) but dumped core on the 3B2. In the code above c should be declared int -- nothing else, int! I can't testify to how this works on all compilers under the sun, but on the 3B2 the compiler treated char as unsigned. It considered the type of (c = getchar()) to be the type of c -- i.e. unsigned char. Since EOF was defined as -1, c received 0xff. But when the value of the assignment was compared to EOF it was coerced back to an int. Since the compiler considered it unsigned, when the value of the assignment was coerced back to an int it became plain old 0xff -- **NO SIGN EXTENSION**. When getchar() finally hit end of file the code compared 0xff to 0xffffffff and got **FALSE**. Sorry to repeat exactly what Guy just said, but it didn't seem to get through. K&R (referring to assignment operators): "The value is the value stored in the left operand after the assignment has taken place." ^^^^^ and "If both operands have arithmetic type, the right operand is converted to the type of the left preparatory to the assignment." ^^^^^^^^^^^ I don't think that leaves any room for doubt. The type of an assignment is supposed to be the type to which the rvalue must be coerced to be stored in the lvalue. H&S state explicitly: "the type of the result is equal to the (unconverted) type of the left operand." ^^^^ I don't have a copy of the ANSI draft, but I would assume they must make this explicit. K&R, in discussing whether sign extension must occur when a char is converted to an int: "Whether or not sign-extension occurs for characters is machine dependent." So there's nothing inherently broken about the way the 3B2 does it. But that code above is very definitely broken. On a 3B2 it will run right past the EOF. It works on a VAX because C on the VAX sign-extends char in promoting it to int. The VAX is probably the *WORST* machine in the world on which to try to decide if something is "clean C" by trying it and seeing if it works. All kinds of dirt work on a VAX. :-) -- Jim Rosenberg CIS: 71515,124 decvax!idis! \ WELL: jer allegra! ---- pitt!amanue!jr BIX: jrosenberg seismo!cmcl2!cadre! /
mcdonald@uiucuxe.cso.uiuc.edu (07/05/87)
>/* Written 12:25 pm Jul 2, 1987 by ron@topaz.rutgers.edu in uiucuxe:comp.lang.c */ > >> The main advantage of this idiom is for "while" statements. The usual >> example is "while ((c = getchar()) != EOF) ...", which cannot be >> written cleanly without the embedded assignment. The use in "if" >> statements often permits one to collapse nested ifs, which can >> *improve* code readability. > >If "c" is of type "char" this is still not written cleanly. EOF is >type int. The assignment into a character causes getchar's int return >to be changed to char, invalidating the following comparison. The >effect is that characters of value 0xFF will cause erroneous end of >file ... >To do this right you need an extra int temporary value > > while ((i = getchar()) != EOF) > or > while( i = getchar(), i != EOF) > >followed by > c = i; How about using the notorious comma operator TWICE: while(i = getchar(), c = i , i != EOF) Is this correct, or would you need while( (i = getchar(), c = i ), i != EOF ) ? As a poor C novice I see no way around this short of putting c = i on a separate line. Doug McDonald
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/05/87)
Let me explain by example why if ( bool_expr == TRUE ) is dangerous (it should be obvious why it's redundant), so that if ( bool_expr ) should be preferred. There is no corresponding problem in C with if ( bool_expr == FALSE ) although I would recommend if ( !bool_expr ) instead. As other have remarked, the proper use for symbolic constants TRUE and FALSE is as values to be assigned to variables (or function returns) that are considered to be inherently Boolean in nature. (Actually, I so strongly consider treating Boolean objects as inherent primitive types to be important that my own definitions are lower-case, to appear just like language keywords.) EXAMPLE: The original UNIX System V Release 2.0 definitions in <stdio.h> were #define feof(p) ((p)->_flag & _IOEOF) #define ferror(p) ((p)->_flag & _IOERR) These are supposed to be treated as Boolean expressions, but their TRUE values are NOT 1. In my version of <stdio.h> I changed these to #define feof(p) (((p)->_flag & _IOEOF) != 0) #define ferror(p) (((p)->_flag & _IOERR) != 0) for safety's sake.
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/05/87)
In article <1213@carthage.swatsun.UUCP> rice@swatsun (Dan Rice) writes: >sphere s1, *s2; >Now, say I refer to s1.o.x and s2->o.y. Does the compiler convert this into >a simple address reference at compile time, or is work performed at runtime? It of course depends on the implementation, but usually s1.o.x will become a direct memory reference while s2->o.y will of necessity (because s2 is variable) turn into a single-level indexed reference (the ->o and .y will usually be folded into a single offset). >Should I define >vector center; >center = s2->o; >if I plan to use s2->o several times? That depends on what you plan to do with it. The above causes a block move of the contents of the vector structure, whereas direct use of s2->o will not necessarily cause much data motion or even much additional overhead. In most cases you needn't allocate temporaries like this just for the sake of "efficiency". In case of bottleneck code, the best approach is to try each proposed efficiency hack individually to see if it makes a difference. Usually it won't, so you're better off keeping the code as intelligible as possible.
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/05/87)
In article <262@auvax.UUCP> rwa@auvax.UUCP (Ross Alexander) writes: >In article <22635@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: >> "getchar" >> will return EOF when it hits end-of-file; EOF (-1) will get converted >> to '\377' ... which compares equal to 255 but not to -1. >Isn't there a little room here for arguement? No, of course not (:-)). The semantics of the expression in question are well understood to agree with Guy Harris's explanation and in fact the forthcoming ANS for C will back him up on this. >the VAX 4.2bsd cc agrees with me... ? The most buggy of the generally-used C compilers, eh? >whilst the SUN 4.2 cc feels otherwise... Is Sun up to SunOS Release 4.2 already? All the argument for your "intuition" of what should happen is beside the point -- there are regular semantics for C expressions, and no legitimate compiler should circumvent them. The whole point of specifications and standards is to define exactly what the rules are.
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/05/87)
In article <13148@topaz.rutgers.edu> ron@topaz.rutgers.edu (Ron Natalie) writes: >Microsoft C (if it really requires NULL to be 0L) is broken ... True, but I doubt that the "large model" MicroSoft C REALLY "requires" that definition. I suspect it was intended to make sloppy code such as func( 1.0, NULL, &a ); "work" even though such code is simply non-portable NO MATTER WHAT rules one tries to impose on the definition for "NULL". (For people who STILL don't understand this, consider that sizeof(char *) need not be the same as sizeof(int *), so the correct size for the second parameter to func() depends on the particular pointer type that func() expects; there is NO universal pointer size in such an implementation.) Personally I would rather see the code BREAK, so that the programmer would LEARN how to code portably and FIX the code for once and for all. (This is the same reason that I chose to make "ordinary" chars unsigned on my Gould implementation of the System V implementation (as opposed to signed on my VAX implementation), even though the native Gould compiler made them signed, presumably to help prop up buggy code from VAX 4.nBSD. I have seen a lot of 3Bx AT&T code assume that chars are unsigned, which is just the opposite bug.) We occasionally hear (in this and the UNIX newsgroups) marketing "justifications" for adapting implementations to certain erroneous coding practices, but the customer is NOT "always right"! In fact, when I evaluate a system I would give NEGATIVE scoring points to those vendors who deliberately pander to such errors.
ka@hropus.UUCP (Kenneth Almquist) (07/06/87)
Let me give a little history here. In the mid 70's, there were two documents describing C: the C Language Tutorial and the C Reference Manual. The C Tutorial stated that the value of an assignment statement was the right hand side, and the C compiler agreed. When I read this, I was impressed with the attention to detail this showed. Unfortunately, I was not so attentive to detail, and failed to note that the C Reference Manual disagreed. So when the stdio library was written, I started writing, "char c; while ((c = getchar()) != EOF) ...", assuming that the test for EOF would work as the C Language Tutorial said it would. Eventually (e. g. around 1979 or 1980), the UNIX Support Group at Bell Labs noticed the discrepancy between the C Reference Manual and the C compilers, and modified the C compilers to agree with the Reference Manual. This generally made the object code produced by the compilers larger, and broke a bunch of existing code in subtle ways (the change was just made, not announced), but certainly the discrepancy between the documentation and the code had to be resolved some how. Probably the BSD Vax compiler is based upon an early version of the AT&T Vax compiler which predated the modifications. As you can probably tell, I would never have made the value of the assign- ment operator be the left hand side. But this is a minor matter compared with, say, the priority of the & and | operators, and Dennis Ritchie endorsed the left hand side approach in the C Reference Manual, even if he contradicted himself elsewhere. It seems a little late to re-fight the battle at this point. Kenneth Almquist
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/06/87)
In article <1219@ius2.cs.cmu.edu> edw@ius2.cs.cmu.edu (Eddie Wyatt) writes:
-... use NULL instead of 0 because there are those implements of C
-(which are wrongly implemented) that don't treat 0 the way they should.
Name one.
DRIEHUIS%HLERUL5.BITNET@wiscvm.wisc.EDU (07/06/87)
First: the subject of NULL being defined as 0 has been explored rather thoroughly over the time I read comp.lang.c, and as far as I'm concerned an implementation of C with NULL being defined as something other than 0 is plainly wrong. Second: the open() call is defined as returning a file descriptor, which is explicitly defined as a small, positive integer. Therefore, checking against minus one is equivalent to checking against being negative. As for the readability, I dislike both non-zero constants (as in 'fd == -1') and symbolic constants (as in 'fd == ERROR'), if the definition of (in this case) a file descriptor specifically says that only *positive* values are valid (it says so on page 159 in K&R for the truly devout). Of course, you should keep in mind that fopen() returns EOF, which is defined as -1, and that assuming that any negative return from fopen() means an error has occurred, is non-portable as far as machines with possibly valid negative pointers are concerned (This is the case on small-model 808x for instance). - Bert Bert Driehuis, LICOR Leiden, The Netherlands <DRIEHUIS@HLERUL5> V.N.G. The Hague <I speak for neither of the above> P.S. While proofreading this letter, I noticed I had written something to the extent that zero equals minus one (I thought: 'x == -1', I wrote down: 'checking against zero'). Now, I remember someone being flamed for implying that if(!p) is equivalent to if(p!=NULL), and retaliating that this provides conclusive proof that C isn't readable. Well, I just proved conclusively that English isn't writable (at least to me) - Bert.
edw@ius2.cs.cmu.edu (Eddie Wyatt) (07/06/87)
In article <8168@brl-adm.ARPA>, DRIEHUIS%HLERUL5.BITNET@wiscvm.wisc.EDU writes: > Of course, you should keep in mind that fopen() returns EOF, > which is defined as -1, and that assuming that any negative > return from fopen() means an error has occurred, is non-portable > as far as machines with possibly valid negative pointers are > concerned (This is the case on small-model 808x for instance). > - Bert > Bert Driehuis, LICOR Leiden, The Netherlands <DRIEHUIS@HLERUL5> > V.N.G. The Hague fopen returns EOF on error??? My man page says it returns NULL (0) if it can't open a file. If what you really meant was open, well my man page says it returns -1 not EOF on error. (This really doesn't belong in comp.lang.c) -- Eddie Wyatt e-mail: edw@ius2.cs.cmu.edu terrorist, cryptography, DES, drugs, cipher, secret, decode, NSA, CIA, NRO.
preece@ccvaxa.UUCP (07/06/87)
stevesu@copper.TEK.COM: > It has been pointed out that my original posting had a blatant > error in it: (!p) and (p != NULL) are not at all equivalent, but > in fact are exactly opposite. I was at first acutely embarrassed, > but I am now flatly amazed that the statements have been argued > so hotly, by some of the best minds on the network, without > noting this crucial fact. ---------- Actually, when I wrote my response to the original note I did notice and correct the error in my own text; I didn't comment on the error, though I chuckled. Why, though, does the presence of the error and your own failure to notice it imply to you that one form or the other is preferable? All it says to me is that MIXING the two forms is dangerous. While we have, here, house coding standards, the bottom line is "Make it fit in with the surrounding code." If you are maintaining a program written in "if (!p)" style, PLEASE, PLEASE don't decide that you want to use the "right" "if (p == NULL)" form in your code; likewise don't use "if (!p)" if the rest of the program uses "if (p == NULL)". Consistency within a program is the most important thing. -- scott preece gould/csd - urbana uucp: ihnp4!uiucdcs!ccvaxa!preece arpa: preece@Gould.com
rwa@auvax.UUCP (Ross Alexander) (07/06/87)
In article <225@amanue.UUCP>, jr@amanue.UUCP (Jim Rosenberg) writes: > In article <262@auvax.UUCP>, rwa@auvax.UUCP (Ross Alexander) writes: > > The following discussion is all relative to [... and other maunderings] I withdraw! I recant! assignment-produces-an-implicit-cast, see K&R Appendix A, 7.14! I wuz rong!!! Please let's save net bandwidth (and my shattered ego). Gads. Everybody's an expert [grumble mutter rhubarb rhubarb rhubarb]. As an aside (I _DO NOT_ propose this be made standard), just as a very personal, off-the-cuff-remark, the rule does seem like a bit of a lose. There must be a good reason for it. What is it? After all, after the store the original value is still sitting around in a register (of course you can dream up architectures where this is not so, yes, I know, I know ), Anyway, it annoys me to see what I think of as a side-effect (the store) throwing away perfectly useful information - and then the first thing that happens after the store is the char expression is re-promoted to int! Waste motion, waste motion... Yes, a clever compiler will do the comparison char-to-char to avoid doing the promotion, because it's equivalent (in a formal sense) to the int-to-int case, but the whole original point was that the assignment introduces a bug through the loss of the out-of-band value (EOF). So as Guy Harris observes, the vax 4.2bsd compiler generates incorrect code. Yet it seems to me that this code is more robust that the `correct' code :-). This is something for the designers of P to think about, I guess. However, the semantics of C are perfectly clear on this, and I was wrong. ...!ihnp4!alberta!auvax!rwa Ross Alexander @ Athabasca University
mpl@sfsup.UUCP (07/06/87)
In article <13112@topaz.rutgers.edu>, ron@topaz.rutgers.edu.UUCP writes: > To do this right you need an extra int temporary value > > while ((i = getchar()) != EOF) > or > while( i = getchar(), i != EOF) > > followed by > c = i; Since when? WHat's wrong with: int c; while ((c = getchar()) != EOF) { /* use c as you would any char variable */ /* because char's are promoted to int ANYWAY */ /* in expressions - no need for a temp variable */ }
jfh@killer.UUCP (07/06/87)
In article <221@amanue.UUCP>, jr@amanue.UUCP (Jim Rosenberg) writes: > In article <13008@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) > writes: > > [... other pet peeves ... ] > > > > 2. Needless use of the comma operator and parenthesis to demonstrate > > manhood to the obliteration of code readability, e.g. > > > > if((fd=open("foo",1)<0) > > > > SCREW this, too difficult, how about writing the code to indicate > > what is going on: > > > > fd = open("foo", 1); > > if(fd == -1) > > I recall many moons ago whilst browsing K&R and before really learning C that I > swore up and down that I would forego such (what I thought at the time to be) > unwarranted over-abbreviation as: > > while ((c = getchar()) != EOF) { > > It didn't take me long once I was actually using C on a regular basis to > realize that forgoing constructs such as the one above is not only ridiculous, > a case can be made that it is actually LESS CLEAR stylistically. Yes, it is > more difficult to read for novices to C. But consider the alternative: > > for (;;) { > c = getchar(); > if (c == EOF) > break; > > I would argue that a strong case can be made that the for loop is actually > LESS CLEAR than the while loop. By announcing the for loop as a forever loop, I guess this is why some programmers make more than others. I find the open() example to be perfectly readable. The while() example is also my preferred method. Some of the other code I preferred to use: struct something { int whoknows; char whatever; }; func (member) struct something *member; { if (member && member->whatever == ILLEGALVALUE) relying on the left-to-right order of && evaluation. I hated PASCAL in college because the above had to be coded if member <> nil then if member^.whatever = ILLEGALVALUE then . . . . Also, I like to perform if-then-else's in expressions some times so the value inside a block of code is more uniform - if (isupper (c) || (islower (c) && (c = toupper (c)))) { ... This is a lousy example for me since I almost never use ctype ... but you get the point. Inside the then part 'c' is an UPPER case letter - if there is an else-part then c is not a letter of any case. My favorite this year is switch (fork ()) { case -1: /* fork() failed */ perror ("some message"); break; case 0: /* in child process */ exec ("my_prog", 0); perror ("some other message"); exit (-1); default: /* in parent process */ who_knows_what (); } I got tired of declaring int fork(); on one line, int child on another and then having to write all of those stupid if-then-else's. This example seems to show the _exact_ nature of the fork() system call. My big beef with the for(;;) example the poster gave is that it is a perfect example of a middle tested loop - and in this case the code to make it a top tested loop is very simple. If you want to use a for() loop, at least use a good one: for (c = getchar ();c != EOF;c = getchar ()) { ... I don't care for this much because of the two getchar calls on the same line - looks wasteful. And besides, while ((c = getchar ()) != EOF) { ... has the exact same behavior and may well optimize to the exact same code. (It can be optimized to exactly the same code as the for() loop). But then I never assume anything about my optimizer. I ask the group, thich do you prefer? if (access (file, 0)) { fd = open (file, 0); if (fd >= 0) good_stuff (); else error_handler (); } else error_handler (); - or - if (access (file, 0) && (fd = open (file, 0)) >= 0) good_stuff () else error_handler (); When I see the first example, I read it in English as If I can access the file, then get a file descriptor for the file from open (); if the file descriptor is legal, then do good_stuff(); else do error_handler(); else do error_handler(); and by the time I am down to the second call to error_handler I have forgotten why the test failed in the first place. The second example I read as If I can access the file and get a legal file descriptor from open, then do good_stuff(); else do error_handler(); I might even be inclined to throw in a || creat (file, 0666) someplace in there just in case I might want to have the file no matter what. This to me is consice code. Anything more is a waste of my time. - John.
henry@utzoo.UUCP (Henry Spencer) (07/06/87)
> Some people (Dijkstra and Gries among them) no longer believe that this > kind of "conservative" test is preferable to the exact test; they base > this on what it takes to prove an implementation of an algorithm to be > correct. Since in the general case such proof requires the use of the ultimate high- level software tool -- a graduate student -- those of us without access to such facilities can safely disregard this argument! :-) :-) -- Mars must wait -- we have un- Henry Spencer @ U of Toronto Zoology finished business on the Moon. {allegra,ihnp4,decvax,pyramid}!utzoo!henry
jpn@teddy.UUCP (John P. Nelson) (07/07/87)
>Microsoft C (if it really requires NULL to be 0L) is broken by all >standards that C compilers have ever been designed to. K&R explicitly >defined 0 comparisons to pointers to be the rule and no one since has >come up with any convincing arguments to the contrary. I don't know, I think this is defensible. The reason that Microsoft did this was so that UNIXisms like: execl(a, b, c, NULL); Continue to work even on the bizzare 8086 architecture. I realize that this is strictly incorrect (NULL should be cast to (char *)), but a lot of existing code depends on this behavier. Of course, pointer assignments and comparisons to (int)0 MUST continue to work properly also, or the compiler is broken (and in fact, they DO work properly)!
dg@wrs.UUCP (David Goodenough) (07/07/87)
In article <1213@carthage.swatsun.UUCP> rice@swatsun (Dan Rice) writes: > Here's a simple question for you. Suppose I have defined structures >containing other structures, i.e., > >typedef struct { > float x, y, z; /* Coordinates of a vector in 3-space */ >} vector; > >typedef struct { > vector o; /* Center */ > float r; /* Radius */ >} sphere; > >sphere s1, *s2; > >Now, say I refer to s1.o.x and s2->o.y. Does the compiler convert this into >a simple address reference at compile time, or is work performed at runtime? It's done at compile time. >Should I define > vector center; > center = s2->o; >if I plan to use s2->o several times? Thanks for any help. You'd do better with vector *center; center = &(s2->o); and refer via center->. You will get two savings out of doing it this way: whenever you do a structure reference e.g. foo.bar the compiler internally has to convert it to (&foo)->bar i.e. it's more eficient to work with the address of a structure; and center = s2->o is a whole structure assignement (12 bytes worth of move on a 68K) whereas center = &(s2->o) is only a pointer assigment (i.e. 4 bytes). NOTE also that these two will have very different results if you start assigning back into center: with your method since center is a complete new copy of the structure center.x = 3.0 would not assign 3.0 into s2->o.x; whereas with pointer work center->x = 3.0 drops the value into s2->o.x - something to be aware of when you're deciding which way to go. -- dg@wrs.UUCP - David Goodenough +---+ | +-+-+ +-+-+ | +---+
mc68020@nonvon.UUCP (mc68020) (07/07/87)
I have been reading all the articles in this series on readable code. I have seen some thouroughly ridiculous and outrageous comments and suggestions, fortunately more than outweighed by the rational, well though out comments of a few of our resident experts. What bothers me, however, is that while all of this discussion about style, readability and portability is educational and even, for the most part, enjoyable, I have seen not one mention of what I consider to be the single most important element of well written, portable code (in *ANY* language, not just C!). That element is DOCUMENTATION. COMMENTS. It is all well and good for the expert C guru to casually dismiss this with the most common (and most heinously WRONG) argument "any programmer good enough to work on this code should be able to figure out what it does." Sure, a good programmer needs to be able to analyze a piece of code. This does not by any means imply that *EVERY* piece of code written should enforce such analysis through lack of proper documentation. Such excuses are precisely that, the excuses or rationalizations of lazy, arrogant programmers. The plain fact of the matter is that any program that is undocumented is a poorly written program, I don't care *WHO* wrote it, how elegant the executable code is, how beautiful the algorithms...without documentation, the program is incomplete. As an example, there is a small C program included in the news 2.11 distribution, under the misc directory, named article.c. I will not name any names, but it was written by a very well known and highly respected member of the UNIX/net community. I am sure that the program does whatever it does elegantly and efficiently. Therein lies the rub: What the **** does the program do? No documentation header, no comments saying: this program does XYZ. There are a few scattered comments in the code which are virtually useless to anyone but a guru level programmer, and probably not of much value to them. This program should NOT have been included in the distribution. What for? So I can take several hours to several days of my time analyzing it to figure out what the **** it does????? Of course, in all fairness, it is almost as well documented as the news software itself. I *HATE* approaching the problem of porting some piece of code from mod.sources to my system, because in 98% of the cases, there is little or no documentation included. It is *WONDERFUL* that someone took the time to write a piece of useful code, and chose to share it with the rest of us. Commendations and kudos to the contributors! I should think, however, that they would be ashamed to place in the public domain incomplete, poorly written programs with THEIR names on them! (by that I am referring the above harped upon issue of undocumented programs being incomplete programs) Frankly, I see two major classes of people foisting this crap off on the world: those who are simply too lazy to do things correctly (probably far and away the largest group), and those whose arrogance causes them to deliberate obfuscate. Yes, I accuse many of the experts, particularly the older experts, of deliberately making their code difficult to read, and to understand. I believe their attitude is something like: "*I* had to suffere through poorly documented code, and no comments, so everyone else should too." I have had some experts say such things to me in so many words. I say to them, and to all of you, that such an attitude is anti-social, counter- productive, rude, and downright vicious. So far as I am concerned, if that is your attitude, then KEEP your damned knowledge, because it is flawed, just as you are flawed. My apologies for the long-widedness and slight flamage. This is an issue I take very personally.
dg@wrs.UUCP (David Goodenough) (07/07/87)
In article <262@auvax.UUCP> rwa@auvax.UUCP (Ross Alexander) writes: >The following discussion is all relative to > > #define EOF -1 > char c; > while ( ( c = getchar() ) != EOF ) { /* do something */ } In the above case I have worked this on several machines (PDP 11/44A, VAX 11/750, 68K, Z80) and in the first three cases I get the following _WARNING_: "Non-portable character comparison" However on the Z80 it gets it wrong (because chars are implicitly unsigned) ..... >I argue >that this is because in this context, the assignment is a side >effect, and isn't intended to `narrow' the value of the expression. >Just to point out where the logical extension of case two gets us, >what is the value of x in this fragment: > > float x; > int i; > > x = 2.0 + ( i = 3.5 ); > >I would say 5.5; others might say 5.0, it seems. But if I _wanted_ >5.0, I would expect to write > > x = 2.0 + (int) ( i = 3.5 ); > >and I appeal to the principle of least astonishment for justification. /* FLAME THROWER OUT!! :-) */ Now hold on just a minute !!!!! - aren't we forgetting something about the order of evaluation rules here: the (i = 3.5) _MUST_ be evaluated first, and evaluates to an _int_ type expression whose value is 3 (i.e. the value assigned into i, so we effectively have: x = 2.0 + 3 and the day that evaluates to 5.5 I'm gonna jump off the Golden Gate: Think about the following: float x; float y; int i; int j; j = (x = 2.0 + (i = 3.5)); (y = 2.0 + (i = 3.5)); Are you going to try to convince me that the two lines above put different values into x and y??????, because that seems to be what is being said here: type coercion works from the INSIDE OUT, NOT the OUTSIDE IN, i.e. let each expression look after itself; i.e. you can't cast the (i = 3.5) based on the fact that it's about to be assigned into a float, it is an integer type expression pure and simple. /* FLAME THROWER AWAY!! :-) */ -- dg@wrs.UUCP - David Goodenough +---+ | +-+-+ +-+-+ | +---+
guy%gorodish@Sun.COM (Guy Harris) (07/07/87)
> I don't know, I think this is defensible. The reason that Microsoft did > this was so that UNIXisms like: > > execl(a, b, c, NULL); > > Continue to work even on the bizzare 8086 architecture. 1) This is NOT a "UNIXism". There are UNIX implementations on which this does NOT work. 2) The 8086 architecture may be bizarre, but this doesn't have a lot to do with pointers and integers not being the same size. There is at least one 68000 C implementation on a UNIX system that provides 16-bit "int"s and 32-bit pointers, and *this* is defensible as well, given the 68000 and 68010's incomplete support for 32-bit arithmetic. (On balance, going with 16-bit "int"s on a 680[01]0, especially in a UNIX system, may not be the right choice, given that: 1) the maximum value representable in an "int", in pre-ANSI C implementations, is the maximum size of an object and the largest value you can use as a size for "malloc" *OR* "realloc" and 2) the 68020 makes it less of a win to have 16-bit "int"s, so you'd either have to sacrifice binary compatibility when upgrading, throw in hacks to make it work, or hobble the 68020 but going with 16-bit "int"s is certainly not INdefensible.) 3) There are probably some architectures that most people would consider more bizarre than the 8086, for which a C implementation would make sense, but where 1) not all pointers are the same size or 2) null pointers are NOT represented as a string of zero-valued bits and, as such, on which you MUST properly cast null pointers when passing them as arguments. Defining NULL as 0L may act as a Band-Aid(TM) for some architectures, but it may merely postpone the day of reckoning in some cases. One might as well do it right in the first place.... > I realize that this is strictly incorrect (NULL should be cast to > (char *)), but a lot of existing code depends on this behavier. It's not a question of '"strictly" incorrect', it's a question of "incorrect". There was some code in 4.1BSD that "depended" on location 0 containing a byte with a value of zero, but, well, that code doesn't work everywhere and has to be fixed. There may be cases where contorting the implementation to cater to bad code causes more problems than it solves. And if you don't supply negative reinforcement, some bad habits are never unlearned. Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
dave@sds.SciCom.MN.ORG (dave schmidt x194) (07/07/87)
> > I don't know, I think this is defensible. The reason that Microsoft did > > this was so that UNIXisms like: > > > > execl(a, b, c, NULL); > > > > Continue to work even on the bizzare 8086 architecture. > > 1) This is NOT a "UNIXism". There are UNIX implementations on which this > does NOT work. Absolutely true. However, looking through my MicroSoft Xenix manual, I can find correct synopses such as int execl( path, arg0, arg1, ..., argN, (char *)0 ) char *path, *arg0, *arg1, ..., *argN; and int wait(stat_loc) int *stat_loc; int wait((int *)0) but I can also find incorrect (or at least misleading) statements such as char *strtok(s1, s2) char *s1, *s2; the description for strtok() states: "... Subsequent calls with NULL for the first argument ..." and char *defread(pattern) char *pattern; the description for defread() states: "... or it [the program] may call defopen with NULL, ..." which certainly implies that strtok(NULL, s2) and defread(NULL) are valid calls. Hopefully, the misleading implications above are peculiar to the MS manuals and are not indicative of what the System V or BSD manuals say. If, however, other manuals mislead the reader in this manner, then it is small wonder that people (a) don't cast NULL when passing it as an argument to functions, (b) regard things such as execl(a,b,c,NULL) as an "UNIXism", (c) think (as I used to, before my enlightenment :-) that #define-ing NULL as something other than 0 (like (char *)0) is correct, since if it were otherwise one couldn't write defread(NULL) which the manual says is OK. Anyway, how about this: if your reference manuals seem to imply that passing NULL to a function without a cast is an OK thing to do, drop me an e-mail message and I'll summarize to the net later if there is sufficient response. Dave Schmidt {cbosgd, ihnp4, rutgers}!meccts!sds!dave
wcs@ho95e.ATT.COM (Bill.Stewart) (07/08/87)
In article <262@auvax.UUCP> rwa@auvax.UUCP (Ross Alexander) writes: : The following discussion is all relative to : : #define EOF -1 : char c; : while ( ( c = getchar() ) != EOF ) { /* do something */ } : : In article <22635@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: : > > The assignment into a character causes getchar's int return : > > to be changed to char, invalidating the following comparison> : > On a machine where "char" is not signed, this won't work even if you : > run it on text consisting entirely of 7-bit characters. "getchar" : > will return EOF when it hits end-of-file; EOF (-1) will get converted : > to '\377' ... which compares equal to 255 but not to -1. : : Isn't there a little room here for arguement? Now of course K&R say : that an expression is coerced to the type it is being assigned to. As with other expressions, the definition of the language specifies the value and type of the expression. For an assignment expression "lvalue = rvalue", the type is the type of the lvalue, and the value is the value assigned to the lvalue. : But isn't assignment, in the strictest sense, a side effect? Yes, in some sense. Consider ++n and n++. Both have the side effect of incrementing n, but they have different values. : and I appeal to the principle of least astonishment for justification. Consider the case char c,d; c = -1; printf( "%d %d\n", c, (d = -1) ); By "least astonishment", it should print the same number twice. On your VAX, that will be -1 -1; on my 3B it will be 255 255, because characters have values in the range 0 - 255, not -128 - 127. While the 255 is mildly surprising, it's a lot less surprising than 255 -1 would be. But yes, people occasionally get surprised when they move code from VAXen to other machines. -- # Bill Stewart, AT&T Bell Labs 2G-202, Holmdel NJ 1-201-949-0705 ihnp4!ho95c!wcs
devine@vianet.UUCP (07/08/87)
In article <13148@topaz.rutgers.edu>, ron@topaz.rutgers.edu (Ron Natalie) writes: > Microsoft C (if it really requires NULL to be 0L) is broken by all > standards that C compilers have ever been designed to. K&R explicitly > defined 0 comparisons to pointers to be the rule and no one since has > come up with any convincing arguments to the contrary. Yes, MS does define NULL to be 0L for large model programs. It is correctly set to the integer constant 0 for small and medium models. The error is compounded by having NULL #define'd like that in *several* include files -- not just stdio.h. That split definition comes from the MS decision [*] to support its linker. This thinking also created the atrocity of "near" and "far" pointers. Bob Devine [*] It also may be argued that this all stems from Intel's decision to be forever backward-compatible with an old chip design.
jr@amanue.UUCP (07/08/87)
In article <1221@ius2.cs.cmu.edu>, edw@ius2.cs.cmu.edu (Eddie Wyatt) writes: > In article <221@amanue.UUCP>, jr@amanue.UUCP (Jim Rosenberg) writes: > > while ((c = getchar()) != EOF) { > > . > > . > > . > > > > ... But consider the alternative: > ^^^^^^^^^^^^^^^ > > > > for (;;) { > > c = getchar(); > > if (c == EOF) > > break; > > . > > . > > . > "the alternative"! Come on there are a thousand and one ways to code > the semantics of the above loop. For example : > > > c = getchar(); > while (c != EOF) > { > . > . > . > c = getchar(); > } > I don't mean to flame you, but as it happens I believe your solution is dead wrong, on two counts. (1) The two loops are *NOT EQUIVALENT*! They may be equivalent to Pascal programmers, but they certainly are not to C programmers. The reason is simply that in your version you must completely forego the use of the continue statement. The fact that break and continue are missing from Pascal is a general class disaster, whereas the fact that they're present in C is a constant joy. A continue statement inside your loop will obviously fail to reexecute getchar(). I suppose you could get around that as follows: for (c = getchar(); c != EOF; c = getchar()) { . . . } which frankly I don't find objectionable. (2) Once again I must protest that your version is really less clear. Think about how *pseducode* for this loop might look: while (the next character is not EOF) { . . do stuff with that character . } As I understand it the whole idea behind structured programming is to build the logic insofar as possible by making boxes, where the control flow is determined by what kind of box you have. If we ignore (1) above your method will work, but it doesn't really *EXPRESS THE THOUGHT* cogently. It is not c I'm really testing for EOF -- c is a handy tool that I use because I have to have a variable name. The real thought here is that it's "the next character" that I'm testing for EOF. By putting the call to getchar() right there in while statement I state clearly that this is a "testing-the-next- character" box -- which your code doesn't. It's hard for me to fathom why the idea of assignment as an operator causes such consternation. It's obviously due to the fact that most progammers learned some other language before they learned C (so did I for that matter) and you get caught in a rut where it becomes hard to escape the habit that assignment is a statement, and so must go on a line by itself. Requiring assingments to be on a line by themselves is no more necessary to code clarity than adding dozens of useless temporary variables to arithmetic expressions because you feel you should never be faced with the sight of 3 right parentheses in a row. I will grant anybody that this is an aesthetic or philosophical argument, and there are bound to be differing points of view. Where I will get upset is with those who believe that legitimate coding techniques like while ((c = getchar()) != EOF) { are mere attempts to show off. For *me* personally, putting the assignment into the condition of the while statement is more clear and says what I mean much more effectively than your version. If you like call it a matter of personal taste. -- Jim Rosenberg CIS: 71515,124 decvax!idis! \ WELL: jer allegra! ---- pitt!amanue!jr BIX: jrosenberg seismo!cmcl2!cadre! /
shaffer@operations.dccs.upenn.edu (Earl Shaffer) (07/08/87)
YEA!!! Finally, someone has the guts to say that bad code is bad code! Code is not a scupture. Commercial companies are interested in a commercial product. It must work, be maintainable, and be tested. People take that ".. real programmers dont comment their code, if it was hard to write it should be hard to understand. " seriously! Are they working for your company? ============================================================================== Earl Shaffer - University of Pennsylvania - Data Communications Department "Time was invented so that everything wouldn't happen at once." Steven Wright ==============================================================================
dsill@NSWC-OAS.arpa (Dave Sill) (07/08/87)
Ross Alexander (auvax!rwa) has been blasted for saying: >Isn't there a little room here for arguement [over the value of an >assignment expression]? But if one reads his next sentence: >Now of course K&R say >that an expression is coerced to the type it is being assigned to. one can see that he was not disputing "the correct behavior" of C compilers implementing K&R, but was asking us to question whether this is consistent with the philosophy of the language. Given this context, his point: >So the coercion for the assignment of `int' to `char' in >this case is a side effect and shouldn't have an affect on the value >of the whole expression `( c = getchar() )'. makes a lot of sense. Another example may help clarify the point. Consider long i, func(); short j; i = j = func(...); Under K&R, the return value of func will be converted to short before the assignment to j takes place. If, however, this value is too large to fit in a short it will be truncated. The value assigned to i, then, will be the one assigned to j. Ross's point, I believe, was that this is probably more likely to cause trouble than if the value of an assignment expression was defined to be the right-hand side. I wouldn't suggest changing the semantics of C in this case, but it's something to keep in mind. -Dave Sill dsill@nswc-oas.arpa The opinions expressed above are those of the author and do not reflect the opinions or policy of the Department of Defense or the U.S. Navy.
ron@topaz.rutgers.edu.UUCP (07/08/87)
If your variable "c" is a character, all of your alternatives are wrong. EOF is an integer -1 return from getchar. If you cast it (or assign, same thing) to char before the test, at best you can hope for is that you will only get misleading information from time to time (in the case that 0xFF was really input) or that it doesn't work at all (where (char) -1 is 0xFF and won't ever equal -1). -Ron
gwyn@brl-smoke.UUCP (07/09/87)
In article <598@nonvon.UUCP> mc68020@nonvon.UUCP (mc68020) writes:
[stuff about insufficient comments]
I think the reason there hasn't been much discussion about code commenting
is because the need for it is well understood and drummed into the heads
of most CS students. You are quite correct in remarking that much code
available in the UNIX community is not very maintainable, with lack of
documentation being a primary factor. Remember, however, that a lot of
the original UNIX code was developed as "fast prototypes" by people who
needed functions for their research projects, so that production quality
was not an issue for them. What is much harder to excuse is the fact that
the code quality and documentation have not been much improved by the folks
who package and commercially license it. One wonders what their job is, if
not to clean up the product before distribution. BWK's "pic" particularly
comes to mind as a very useful tool that has some incredible nonportable
"quick hacks" that should have been fixed inside AT&T, not by me and others
who receive the code after forking over a lot of money.
Another solution might be for even the researchy types to exert care to
make their code high-quality and as portable as possible. I've found that
it doesn't take much longer to produce quality code, and in the long run
EVEN FOR ONE'S PERSONAL USE it saves time to do so. Many's the time when
I've thanked the DAG of past years for anticipating future maintenance
questions and providing helpful information in the original sources.
I haven't even mentioned structured software design methodology, which
inherently produces accurate, complete software documentation. (UNIX
types tend to resist doing things in an organized manner.)
garys@bunker.UUCP (Gary M. Samuelson) (07/09/87)
In article <598@nonvon.UUCP> mc68020@nonvon.UUCP (mc68020) writes: > ... while all of this discussion about style, >readability and portability is educational and even, for the most part, >enjoyable, I have seen not one mention of what I consider to be the single >most important element of well written, portable code (in *ANY* language, >not just C!). That element is DOCUMENTATION. COMMENTS. I heartily agree with the above and with most of what followed (deleted to save space). Since code will be read more times than written (hopefully, it will be written only once), it should be written in such a way that it is easy to read. The headaches you save may be your own. In particular, one should resist the temptation to be clever just for the sake of cleverness. Six months later, you won't remember why you yourself wrote that bizarre piece of code, or why it works. Even if you do, the peculiar circumstances that made it work then don't apply anymore. > Frankly, I see two major classes of people foisting this crap off on the >world: those who are simply too lazy to do things correctly (probably far >and away the largest group), and those whose arrogance causes them to >deliberate obfuscate. Well, there is a third class. Some of us are actually not permitted to "do it right." Out in the "real world," as they called it when I was still a student, we have things called schedules and deadlines. Documentation doesn't get written because it takes time and might, the bosses say, delay the introduction of our product past the time when it would make economic sense to produce it. Now I personally think this is shortsighted, and penny-wise-but-pound-foolish, and that investing time in documentation would save more than enough development time to justify it, even on the short-to-moderate term, but I don't know how to prove it to the powers that be. Those one or two levels up say that they believe in the value of documentation, but somewhere up the line the commitment is not there. "Yes, documentation is important, but so-and-so customer is screaming for such-and-such a feature, so we can't schedule time to write anything but code." I'm sure that there are published studies which show that writing good documentation results in reduced development time, fewer bugs, and ease of maintenance. Pointers, please? But even if I present all the theory in the world, and point to my own experience (MY software is always within a month of schedule), the reply is always the same: "So-and-so customer is screaming... we're going to lose a several million dollar contract..." But at some point improving the car is no longer worthwhile; it's time to build a plane. And while we're working on the plane, someone has to start thinking about the rocketship. People -- managers being no exception -- tend to believe what they want to, and don't want to change (they would have to admit being wrong in order to change). Not even real life examples are always persuasive. In one case, I said to the boss three levels up, "Behold, I did it my way, even the way I have been recommending as a better way, and lo, the project was done on time, yea, even early." (On time around here is rare; early is unheard of.) And this person actually told me that if I had done it the Traditional way, it would have take even less time. An unassailable position, false though it be. Well, managers have their problems, too. Managers have to be able to measure progress, and it's hard to measure the progress in a software development effort, especially when software, per se, is not currently being developed, but documentation is. Lines of code per day is a rotten metric, but what else is there? LOC is zero during documentation, and artificially high for inefficient programmers (if Goofus writes 100 lines to perform a function that Gallant's 10 line function performs, it's Goofus who appears more productive by the LOC metric). Some measurement more like features per fortnight is needed, but it's still going to be zero during the documentation phase. Then, when Goofus puts in lots of overtime to clean up what he should have done right in the first place, he looks like the hero, and he gets the bonus or the promotion. Gallant, who works much more efficiently, and has therefore completed his own tasks within a normal work week, and still had time to help others, gets criticized for not putting out. To change all this, you have to convince managers to start managing differently. So you say something like this: "Using our traditional ways of development, this project is going to take two years (even though the Old Guard who have been here forever have committed themselves to a one year schedule). Using the new state-of-the-art development techniques, we (essentially, the new kids on the block) will complete the project in 18 months. This is six months more than the Old Guard said it would take, but six months less than what it will really take them, based on the fact that every project done by the Old Guard has taken at least twice as long as the schedule said. Since we are going to spend two-thirds of our time planning and documenting, and only one-third coding, if you use the metrics you are accustomed to using (e.g., lines of code), there won't be any measurable progress for the first 12 months." And the manager says, "I see. You claim that our most senior, most experienced engineers won't make their schedule. You make this claim with less than half the years of experience they have. Further, you make this claim before the project even starts; before they have run into any problems. You further claim that you will be able to meet your proposed schedule, even though the project hasn't started, and even though you don't know what problems lie ahead of you. And you admit that for the first 12 months, I won't be able to show *my* superiors any of the kind of progress that they have come to expect, but after those 12 months are up, when the Old Guard says they will be *done*, you will be ready to *start* coding. And besides all that, you expect me to measure your progress in a manner which you yourself will prescribe." "Well, yes, that about sums it up," you are forced to admit. And the manager says, "Well, when you have a few more years of experience then we will see what you can do when you are given a project to manage. In the meantime, you'll have to work with the Old Guard and do things their way." And then you go off and learn how to do things the old way, and spend the rest of your life holding one hand over your mouth (to keep from complaining too much -- one has to have a good attitude, you know) and the other on your stomach (to keep from retching every time you have to make your software interface with spaghetti code that should have been re-written -- I won't say re-designed, because it hasn't been designed the first time yet -- years ago). > Yes, I accuse many of the experts, particularly the older experts, >of deliberately making their code difficult to read, and to understand. >I believe their attitude is something like: "*I* had to suffer through >poorly documented code, and no comments, so everyone else should too." Well, I am not one of the older experts, but I have to confess to doing what you say, but not for the reason you state. I have written things which were difficult to read not to confuse anyone, but because I was trying to be "clever." I have repented of such things, and try to suppress that temptation now. Ironically, some of these tricks I gleaned from this network. Gary Samuelson
rwhite@nu3b2.UUCP (07/10/87)
In general x = y = z as a "C" statment is simply a short way to write y = z; x = y; If we make z long, y short, and x int and we invoke the pruposed change we get the unique situation that any of the following sequences MAY or MAY NOT evaluate as true on a machine depending only on the size of the types: after x = y = z; ( x == y || y == z || x == z) WILL BE FALSE ON SOME MACHINES Part of the idea of x = y = z where y is the smallest range(ed) type is that x and y WILL BE EQUAL on subsiquent tests for all ranges and returns of z. If x is the smallest range(d) type x and y WILL BE EQUAL only for those ranges and returns of z which are valid for x. This is one of the core issues of return validation under "C". The question can be simplified to: Do you think: y = z; x = y; if (x == y) {...}; AND : x = y = z; if (x == y) {...}; shoul behave the in a identical manner on all machines? Before you say no, consider flag masking and complex algebra. Simple functions like: #define FLAGPART 0x003f #define ERROR 0x0010 ... if ((tempval &= FLAGPART) != ERROR) { /* Process Other VALID Flags */ } else { /* Process Error Flags */ } Yes, there are other ways to write this, but as the things get more complex, how would you like to descover that all your temporary variables contain masks instead of values. Or even worse since ++x is identical to x += 1 and x = x + 1 then l = n[++x] equals l = n[(x += 1)] always yeilding the first element of n or l = n[(x = x + 1)] with x as enum and n demensioned in the enum type maxenum which is undefined unless this trick is applied to enums with special quantities [like char] which wrap in their domain if x = makenum n[++x] is OUT OF BOUNDS in the PERPOSED system [max + 1] but a valid wrap in STANDARD "C" To the claim that x = y = z is too prone to simple types without the mod, I directly say: 1) PAY ATTENTION WHEN YOU CODE. 2) IF YOU ACCEDENTLY TEND TO WRITE y = x = z you will probably write x = z = y or x = y; y = z; and miss the difference, it is an OVERSITE of equal precidence and equal danger. If you try to "protect" the programmer until assignment ORDER dosn't matter [and to put it simply that IS what you are trying to do] then your results wont matter either. The SHORTCUTS in "C", like multiple concatinated assignment and all the opp-equals [etc], are for ease of coding without excess size or complexity they never were intended to prevent the careless from making mistakes. If you add, and keep adding, protections you will have COBOL when you are finished. The reasons things in "C" have a "scanning" or "grouping" direction is because they are scanned and/or grouped out to their EXPLICIT form. x = y = z is NOT give x and y the value of z, it IS give y the value of z and then give x the value of y, [you will note that it groups RIGHT TO LEFT] this is correct an should not be changed. The value of this is obvious, contains no abstractions, and is not dependant on anything other than programmer intent. Robert. Disclaimer: My mind is so fragmented by random excursions into a wilderness of abstractions and incipient ideas that the practical purposes of the moment are often submerged in my consciousness and I don't know what I'm doing. [my employers certainly have no idea]
ggs@ulysses.homer.nj.att.com (Griff Smith) (07/10/87)
In article <6087@brl-smoke.ARPA>, gwyn@brl-smoke.UUCP writes: > In article <598@nonvon.UUCP> mc68020@nonvon.UUCP (mc68020) writes: > [stuff about insufficient comments] > > I think the reason there hasn't been much discussion about code commenting > is because the need for it is well understood and drummed into the heads > of most CS students. I hope the students you know of are the norm. When I was teaching a class the only thing that got their attention was an ultimatum: "If I can't understand your program in ten minutes, your grade is no better than a C". > Remember, however, that a lot of > the original UNIX code was developed as "fast prototypes" by people who > needed functions for their research projects, so that production quality > was not an issue for them. What is much harder to excuse is the fact that > the code quality and documentation have not been much improved by the folks > who package and commercially license it. One wonders what their job is, if > not to clean up the product before distribution. Sigh... My spies at Summit tell me that part of the clean-up consists of removing all author names and all attempts at humor. There is now a company-wide "quality" campaign, however, so maybe some of it will surface. A few years ago there was resistance to changing anything if it didn't improve the benchmarks. It took me two years to convince people that a command with ten bugs in 500 lines of code was broken. I kept getting the comment that "there haven't been any complaints, so nobody must be using the features that are broken". I was also told that the required feature proposals, design reviews and quality assurance documentation would be too expensive. I finally gave them a fixed version accompanied by a feature verification script and got it accepted. Good comments, Doug. Second the motion about doing it right the first time. -- Griff Smith AT&T (Bell Laboratories), Murray Hill Phone: 1-201-582-7736 UUCP: {allegra|ihnp4}!ulysses!ggs Internet: ggs@ulysses.uucp
Leisner.Henr@Xerox.COM (marty) (07/10/87)
>RE: If I understand correctly, according to K&R the "usual arithmetic >conversions" will be applied to the operands of the conditional >operator causing the int 0 to be converted to long int 0. I've found it is often a bad idea to assume these funny type conversions will take place. If it doesn't do what you expect, this is one of the worst possible bugs to track down. C has the power to let you do anything you want -- including shoot yourself in the foot. The PC memory models make life rougher for the programmer, but it also forces you to compare apples to apples. A lot of C code with the implicit assumptions sizeof(char *) == sizeof(int) is very hard to port. Also, it is bad practice to define NULL to be something memory model dependent. A better practice is: #define NULL (char *) 0 This automatically takes care of the sizeof dependencies. This gets to be a major issue when routines are being passed and/or return NULL. marty GV: leisner.henr NS: martin leisner:henr801c:xerox UUCP: martyl@rockvax.uucp
ron@topaz.rutgers.edu (Ron Natalie) (07/10/87)
Yes, but you can't use NULL defined to be anything other than 0 if you hope to use near and far pointers. They've defeated themselves by trying to guess how the user was going to use NULL ahead of time. -Ron
rice@pilchuck.Data-IO.COM (Ken Rice) (07/10/87)
Putting an assignment statement into a control statement like this
while( c=test() != EOF )
;
Makes for readable code, I agree--but can play havoc with maintainability.
If this code breaks, it's can be hard to single-step through this section
and find out what "c" is after test() (depends upon your tools, of course).
I often have to rewrite to separate things into visible parts for maintenance
like was suggested
c=test();
while(c!=EOF)
{
c=test();
}
Now, I can see what's going on with my debugger.
The problem is that, by changing the code, I may have broken it more. It's
different now and, by definition, untested. Should I but it back? That
risks breaking it too. Should I leave it? Should I have been a Forest
Ranger?
This is a case where one quality issue--readability--can reduce the level
of another quality issue. Readability is my usual soapbox, until I run
into this crap
return(x=test());
Then, I want to kill.
Let's write readable and MAINTAINABLE code; Code that we won't have to
change to see how it works. If it reads well and fixes lousy, it's wrong.
Ken Rice
tim@praxis.co.uk (Tim Magee) (07/10/87)
I'm catching up on comp.lang.c, due to a break in our news supply. this reply may duplicate something I haven't seen yet. From the tenor of previous articles, though, I doubt it. In article <1941@zeus.TEK.COM> dant@tekla.UUCP (Dan Tilque) writes: - Ron Natalie writes: - > I also wonder about people who define TRUE to be any - >thing, since it leads to things like - > if( bool == TRUE ) - >which is different than - > if(!bool) - I thought that TRUE and FALSE should be: - #define FALSE 0 - #define TRUE !FALSE - With these #defines the above two statements are equivalent. Are you absolutely sure? suppose 'bool' is TRUE, then the first says: if (TRUE == TRUE) while the second says if (!TRUE), or if (!!FALSE). Are you still sure? - >Generally, I use !p when I'm dealing with things that are supposed - >boolean values like - > if(!isdigit(c)) - It's often easy to miss a single character (especially one that doesn't - stand out like the "!") when quickly scanning code. - if (isdigit(c) == TRUE) - will compile to the same object code on almost every compiler... I quote from the SUN 3.2 UN*X manual page for is****, the ctype macros: 'Each is a predicate returning non-zero for true, zero for false.' The two examples may well compile to the same object code. However the second example won't do anything like it seems to be aimed at achieving, because the ctype macros return the result of AND-ing integer constants of various values with members of an external array. A hallmark of readable code is that it does what it appears to say! The reason most people write obscure C is because they think it's faster. Why do they care? I guess most C programmers work on UN*X or a poor man's copy, and the amount of time UN*X spends executing other peoples' code is large enough to make your trivial 50 nanoseconds saving worth nothing. In the case of the 'isdigit' above, and making 'if (!isdigit(c))' less unreadable, how do you feel about: #define NOT ! ... if (NOT isdigit(c)) { Tim M. -- Tim Magee ...........P[{}([{>)}>>)}<(>]([)>}<[([{<[>)}}>)>]})]}[({>)>}........... "Eliminate the impossible, my dear Watson, and what remains..." "Is still an uncountably infinite set, Holmes."
tim@amdcad.AMD.COM (Tim Olson) (07/10/87)
In article <8249@brl-adm.ARPA> Leisner.Henr@Xerox.COM (marty) writes: >Also, it is bad practice to define NULL to be something memory model >dependent. A better practice is: > >#define NULL (char *) 0 > >This automatically takes care of the sizeof dependencies. This gets to >be a major issue when routines are being passed and/or return NULL. AARRRGH!!! *PLEASE* people -- we've been over this time after time: 1) #define NULL 0 2) When using NULL as a parameter, cast it to the *correct* type The above definition of NULL will choke on systems which have different bit patterns for different pointer types. -- Tim Olson Advanced Micro Devices (tim@amdcad.amd.com)
blarson@castor.usc.edu (Bob Larson) (07/10/87)
In article <2365@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes:
Paraphase: Managers frequently don't see time spend writing documentation
as progress to the goal of a completed project.
True. This also applies to reading documentation, only you don't have
any physical work to point to after you are done. The people who
can't see spending the time reading new manuals as they become
available tend to be the same ones that treat you as a guru when you
use the knolage gained. Even harder to directly prove is the benifits
of reading newsgroups such as comp.lang.c. (With manuals, you can
occasionally point out things like "the answer is in the chapter on
wildcards in the primos reference manual".)
[Fortuanatly, I have an manager who knows the benifits of writing
readable code and doesn't breath over my shoulder all the time.]
--
Bob Larson Arpa: Blarson@Ecla.Usc.Edu
Uucp: {sdcrdcf,seismo!cit-vax}!oberon!castor!blarson
"How well do we use our freedom to choose the illusions we create?" -- Timbuk3
dg@wrs.UUCP (David Goodenough) (07/10/87)
In article <228@amanue.UUCP> jr@amanue.UUCP (Jim Rosenberg) writes: >It's hard for me to fathom why the idea of assignment as an operator causes >such consternation. It's obviously due to the fact that most progammers >learned some other language before they learned C (so did I for that matter) >and you get caught in a rut where it becomes hard to escape the habit that >assignment is a statement, and so must go on a line by itself. Requiring >assingments to be on a line by themselves is no more necessary to code >clarity than adding dozens of useless temporary variables to arithmetic >expressions because you feel you should never be faced with the sight of >3 right parentheses in a row. _WELL_SAID_!! I agree totally - I grew up (god forbid) on a mixture of BASIC, FORTRAN, and ALGOL, and when I finally latched onto how much fun I could have with imbedded assignments, I've never looked back since. It _IS_ necessary to use a little caution, because they can be overdone a = (ptr = expr[i = 3])->foo; kind of thing (!!), but when used in moderation I find them _VERY_ useful from the point of readability. For those of you that don't agree - I've got my asbestos suit and fire extinguisher ready :-) -- dg@wrs.UUCP - David Goodenough +---+ | +-+-+ +-+-+ | +---+
mat@mtx5a.COM (m.terribile) (07/11/87)
> "the alternative"! Come on there are a thousand and one ways to code > the semantics of the above loop. For example : > > c = getchar(); > while (c != EOF) > { > . > . > . > c = getchar(); > } > > Look, no assignments in the conditionals, no hidden gotos (break). > For the people that argue the first form is bad, this would > probably be the approach they would take. C'mon! This is everything that is wrong with Pascal. The data for the first pass through the loop are gathered by operation X in location A. The data for the second pass of the loop are gathered by identical operation X in widely separated location B. Give us a break, fer pitsaeks! -- from Mole End Mark Terribile (scrape .. dig ) mtx5b!mat (Please mail to mtx5b!mat, NOT mtx5a! mat, or to mtx5a!mtx5b!mat) (mtx5b!mole-end!mat will also reach me) ,.. .,, ,,, ..,***_*.
peter@sugar.UUCP (Peter DaSilva) (07/11/87)
> defined. Now, if you're dealing with a mentality that would > do something like > #define TRUE 0 /* don't try this at home, kids */ > then you've got major problems, and it wouldn't help you if that > misguided individual had avoided typedefs and/or #defines. I've seen this one in an otherwise mediocre-but-not-gross program written by someone who didn't grok the return value from strcmp() in all fullness. Come to think of it, i did have other major problems at the time. More of politics than programing though. -- -- Peter da Silva `-_-' ...!seismo!soma!uhnix1!sugar!peter (I said, NO PHOTOS!)
zeeff@b-tech.UUCP (Jon Zeeff) (07/11/87)
In article <598@nonvon.UUCP> mc68020@nonvon.UUCP (mc68020) writes: > > As an example, there is a small C program included in the news 2.11 >distribution, under the misc directory, named article.c. I will not name > Frankly, I see two major classes of people foisting this crap off on the >world: those who are simply too lazy to do things correctly (probably far >and away the largest group), and those whose arrogance causes them to >deliberate obfuscate. I can see why you didn't use your real name in your article. How about a class of people who were very generous in working on the news code at all and had a limited amount of "free" time to spend on it. They spent what time they had where they felt it was best used. Personally, I'm grateful. Why don't *you* write better documenatation for it (or quit using it if you don't like it). -- Jon Zeeff Branch Technology, Ann Arbor, MI seismo!umix!b-tech!zeeff zeeff%b-tech.uucp@umix.cc.umich.edu -- Communication begets knowledge and knowledge begets power --
rwhite@nu3b2.UUCP (Robert C. White Jr.) (07/11/87)
I will not include the entire thing, but you ar correct to convert while ((x = func()) == y) {...} into x = func() while (x == y) {...; x = func();} for debugging purposes. To your question: "should I leave it that way?" I SAY NO. The acceptability of the expansion is required by most rules governing these things. The expanded version, however, may take MUCH more space when done on a whole program. In a sense, the structure [at object level] is poluted by the extra call. The entire code group, even durring debugging, is more CONSICE and easy to deal with in the compresses form. The aproach to debugging the compressed form is actually easier when done from the correct angle. I may be a trick nobody ever explained, but the invocation level and values expressed in the return statment are generally better debugging points when they can be compared to the assigned-to variable's value in the macro statment [it all gets displayed together]. The expansion should be saved for times when you are checking the algorythm itself. [algorythm analisys should NEVER be done with a debugging tool (there is a controversial opinion for you :-) because you will often take value progression for granted, or miss a multi-path variant because you couldn't massage the value correctly] ALWAYS compress up-to but not as far as the unreadability threshold. You should be able to read the line once and get the executition path the first time, if not the functional behavior. (-: give the optomizer a break eh? ;-) As if you really ever cared what I thought.... 8-) Robert. Disclaimer: My mind is so fragmented by random excursions into a wilderness of abstractions and incipient ideas that the practical purposes of the moment are often submerged in my consciousness and I don't know what I'm doing. [my employers certainly have no idea]
mouse@mcgill-vision.UUCP (der Mouse) (07/11/87)
In article <849@tjalk.cs.vu.nl>, rblieva@cs.vu.nl (Roemer b Lievaart) writes: >>> if( bool == TRUE ) >>> [which] is different than >>> if(!bool) > Of course it's different. If bool can only be 0 or TRUE, If it were possible to guarantee somehow that bool could take on only the values 0 and TRUE, we'd be nearly home. Unfortunately, one-bit unsigned bitfields are ugly to declare (must be structure members), uncommon, and usually inefficient. Therefore, we are, generally, stuck with using types that can take on more than two values. > To avoid some misunderstandings: > isdigit(), isalpha(), etc. > do NOT just return FALSE or "TRUE" (1)!!! > if ( isalpha('a') ) puts("yes") ; else puts("no") ; > if ( isalpha('a') == TRUE ) puts("yes") ; else puts("no") ; [prints] > yes > no > Do you get it? isalpha('a') is not true! $%&#?! > It's not true, it's 2. isalpha('a') *is* true. It is not TRUE. True, in C, is defined to be non-zero. 2, therefore, is true. That's why the first if worked correctly, or should I say unsurprisingly. > However, I must say I'm very against using '0' instead of NULL. > [clarity of intended type argument] I agree in principle. Unfortunately, I disagree in practice. My reasons are strictly pragmatic: (a) too many C implementations have NULL defined as something wrong (ie, other than plain unadorned 0), (b) using 0 doesn't mean I have to include some .h file, and (c) using 0 makes it plain to someone trying to debug the code and/or a port of it that the problem is *not* a mis-defined NULL. > Our lint complains about: > main() { bar(NULL); } > bar(foo) char *foo; {...} > [Lint's complaint should be <suggestion>]. Instead lint says: > "bar, arg. 1 used inconsistently ..." > Which is not true. I didn't use the argument inconsistently, I > passed a NULL-pointer which is a correct pointer. No you didn't, you passed the integer zero (assuming a correct definition of NULL), which is no sort of pointer at all. Lint is perfectly correct. > char *strings[] = { "one", "two", "three", NULL } ; > This won't pass our compiler. Then either your compiler or the definition of NULL that you're getting is broken, I would say. (No definitive reference comes to mind; anybody care to produce one?) der Mouse (mouse@mcgill-vision.uucp)
mouse@mcgill-vision.UUCP (der Mouse) (07/11/87)
In article <1219@ius2.cs.cmu.edu>, edw@ius2.cs.cmu.edu (Eddie Wyatt) writes: >>> [ stuff re syscall(...) < 0 versus syscall(...) == -1 ] >> Almost agreed: but if a negative return code other than -1 is >> returned the code doesn't react the same. > I can think of no Unix system call that doesn't return -1 on error. > So I would say that it's a pretty good bet that "if (call(...) < 0)" > and "if (call(...) == -1)" will act the same in all cases. ( First point: this is drifting off of C and into UNIX, so I'm trying to move it to comp.unix.questions. ) Well, yes, all syscalls return -1 on error. However, that is not to say that none ever return negative values except for an error return of -1. In fact, I was surprised by this recently. I was using lseek() on /dev/kmem on a VAX, and it was (correctly) returning success values on the order of 0x80020000, which were negative! I had to check for -1 explicitly instead of my usual check for <0 (yes, I am in the <0 camp as far as actual coding practice goes). der Mouse (mouse@mcgill-vision.uucp)
gwyn@brl-smoke.ARPA (Doug Gwyn ) (07/12/87)
In article <654@pilchuck.Data-IO.COM> rice@pilchuck.Data-IO.COM (Ken Rice) writes: > while( c=test() != EOF ) >If this code breaks, ... Of course, it's already broken. Presumably you meant while( (c = test()) != EOF ) Don't feel bad; not long after commenting on (!p) vs. (p != NULL), I made a similar slip and wrote that (1 == 0) was precisely 1; I was actually talking about the example (0 == 0) but I (and others) didn't notice my typo. It does add support to the notion that code should be written as straightforwardly as possible; I admit that it's harder to evaluate (1 == 0) mentally than to evaluate 1 -- indeed that's why I was making that response in the first place. > c=test(); > while(c!=EOF) > { > > c=test(); > } Ugh, this Pascalism is horrible! There is conceptually a single operation ("get next character") being performed over and over until the operation fails (EOF). Writing the operation in two places is not only conceptually more difficult, it make it more likely that a future change (perhaps from c=test() to c=GetNextChar()) will miss one of the repeated occurrences, introducing a perhaps subtle bug. The standard C idiom while ( (c = get_next()) != EOF ) do_stuff_with( c ); (with `c' an (int), not a (char)!) may look complicated, but after one gains experience it seems like the "obvious", natural way to write this. Perhaps an ideal programming language would make this something like: until Get_Next_Character named `c' indicates No_More_Chars, Do_Stuff_With `c' but that's not far from the way one should read the C idiom. > return(x=test()); >Then, I want to kill. Especially since 99 times out of 100 this should have been return test(); >Let's write readable and MAINTAINABLE code; Code that we won't have to >change to see how it works. If it reads well and fixes lousy, it's wrong. I agree with the desire but dispute that the Pascalism is more fixable.
greg@utcsri.UUCP (07/12/87)
In article <47000012@uiucuxe> mcdonald@uiucuxe.cso.uiuc.edu writes: >>> The main advantage of this idiom is for "while" statements. The usual >>> example is "while ((c = getchar()) != EOF) ...", which cannot be >>If "c" is of type "char" this is still not written cleanly. EOF is >>type int. The assignment into a character causes getchar's int return >>to be changed to char, invalidating the following comparison. The >>effect is that characters of value 0xFF will cause erroneous end of >>file ... >>To do this right you need an extra int temporary value >> while( i = getchar(), i != EOF){ >> c = i; > >How about using the notorious comma operator TWICE: > > while(i = getchar(), c = i , i != EOF) > >Is this correct, or would you need > > while( (i = getchar(), c = i ), i != EOF ) ? > Both are correct, (a,b,c) is equivalent to ((a,b),c). But WHY DO YOU NEED char c IN THE FIRST PLACE??? There is no reason to use char instead of int, except to save space in arrays or structs. Simple auto variables should be declared as int. Using a char instead of int in this case will rarely save space, and will often produce slower code (grotty 8088 machines notwithstanding). Not to mention all the EOF pitfalls we've been talking about (oops I mentioned it). The mnemonic effect of making the variable a 'char' because you are using it to store a character is not really worth the trouble. If the type had been called 'byte' we would be using it less often and having fewer problems. >As a poor C novice I see no way around this short of putting c = i >on a separate line. And there is nothing wrong with that, is there? -- ---------------------------------------------------------------------- Greg Smith University of Toronto UUCP: ..utzoo!utcsri!greg Have vAX, will hack...
rbutterworth@orchid.UUCP (07/13/87)
In article <17466@amdcad.AMD.COM>, tim@amdcad.AMD.COM (Tim Olson) writes: > In article <8249@brl-adm.ARPA> Leisner.Henr@Xerox.COM (marty) writes: > >#define NULL (char *) 0 > AARRRGH!!! *PLEASE* people -- we've been over this time after time: > 1) #define NULL 0 This has got to be the most discussed topic in this news group. Every few months it comes up again and again. Dozens of articles state one thing, dozens of others say something else, and eventually it always boils down to the fact that "#define NULL 0" is always correct, "#define NULL ((void*)0)" is (unfortunately) acceptable on some compilers, and everything else is wrong. Considering that this is an open-and-shut case of a simple fact in the language, isn't it amazing how much it is discussed and argued about? Clearly there is something wrong with the concept of NULL, or there wouldn't be such confusion. NULL was introduced as an attempt at making the use of the literal 0 a little more obvious. e.g. in "p=NULL;" vs. "p=0;", the use of NULL indicates that a null-pointer is intended and so reminds the reader that "p" is a pointer of some kind and not an integer. But note that by "indicates" I mean that it indicates it to a human being reading the code, not to the compiler. It is simply a documentation device; it has no such special meaning in the language and to the compiler it is simply treated the same as the literal 0. To me, all this confusion indicates that perhaps it was a mistake to have ever defined NULL in the first place. Surely the number of people that have been fooled by this simple device far exceeds the usefulness that was originally intended. If I were god, when I wrote K&R, I think I'd either build NULL into the language, or I would use the following definition of NULL: #define NULL(type) ((type)0) In the former case "p=NULL" would work fine, but "func(NULL)" would produce an error since there is no type for it to be coerced to. In the latter case, the user would be forced to code "p=NULL(char*);" or "func(NULL(int*));". Either way there wouldn't be any of this confusion that we have now, and will probably always have. (Actually with ANSI officially supporting "(void*)0", things will probably be even more confusing than they ever were.) I for one will never use NULL in anything I write. I'm sure no one could have forseen this when it was first introduced, but in retrospect it really is a half-assed solution that has caused far more problems than it has solved.
edw@ius2.cs.cmu.edu (Eddie Wyatt) (07/13/87)
In article <228@amanue.UUCP>, jr@amanue.UUCP (Jim Rosenberg) writes: > > > > c = getchar(); > > while (c != EOF) > > { > > . > > . > > . > > c = getchar(); > > } > > > > > I don't mean to flame you, but as it happens I believe your solution is > dead wrong, on two counts. (1) The two loops are *NOT EQUIVALENT*! They > may be equivalent to Pascal programmers, but they certainly are not to C > programmers. The reason is simply that in your version you must completely > forego the use of the continue statement. The fact that break and continue > are missing from Pascal is a general class disaster, whereas the fact that > they're present in C is a constant joy. A continue statement inside your > loop will obviously fail to reexecute getchar(). I suppose you could get > around that as follows: > > (2) Once again I must protest that your version is really less clear. Think > about how *pseducode* for this loop might look: > > while (the next character is not EOF) { > . > . do stuff with that character > . > } > > Jim Rosenberg > CIS: 71515,124 decvax!idis! \ > WELL: jer allegra! ---- pitt!amanue!jr > BIX: jrosenberg seismo!cmcl2!cadre! / 1) Next time, don't quote me out of context!!! I say I like while((c = getchar()) != EOF) over my proposed alternative. 2) This posting was more in response to your absolutism "the alternative". 3) The altered loop will work for all but "gotos" and "continues" Also the loop could be made to work with continues if one made the substitution "{ c = getchar(); continue; }" for "continue" (what a hack). 4) I think you have missed the points others have been trying to make. o. Novices do have problems with the construct of assignments in conditions. o. it deviates from the notion that expressions do not have side effects. (call it a Pascalism if you will). Final comment, read the f*ck*ng article and think before you post!!!! -- Eddie Wyatt e-mail: edw@ius2.cs.cmu.edu terrorist, cryptography, DES, drugs, cipher, secret, decode, NSA, CIA, NRO.
mouse@mcgill-vision.UUCP (der Mouse) (07/13/87)
In article <1597@sfsup.UUCP>, mpl@sfsup.UUCP writes: > In article <13112@topaz.rutgers.edu>, ron@topaz.rutgers.edu.UUCP writes: >> To do this right you need an extra int temporary value >> while ((i = getchar()) != EOF) >> followed by c = i; > Since when? WHat's wrong with: > int c; > while ((c = getchar()) != EOF) { > /* use c as you would any char variable */ > /* because char's are promoted to int ANYWAY */ > /* in expressions - no need for a temp variable */ > } The difference becomes significant if you ever take the address of c. For example, write(fd,&c,1); takes on an entirely different meaning. If c is a char, this is portable (provided sizeof(char)==1 -- are there any machines on which this is not true?), but if c is an int you get what you probably expect on a little-endian and it breaks mysteriously on a big-endian. der Mouse (mouse@mcgill-vision.uucp)
dsill@NSWC-OAS.arpa (Dave Sill) (07/13/87)
John Haugh <killer!jfh> wrote: >I ask the group, [which] do you prefer? > > if (access (file, 0)) { > fd = open (file, 0); > if (fd >= 0) > good_stuff (); > else > error_handler (); > } else > error_handler (); > > - or - > > if (access (file, 0) && (fd = open (file, 0)) >= 0) > good_stuff () > else > error_handler (); In general I agree with John. When possible, complex nested conditional structures should be rewritten as complex conditional expressions in a single structure. However, one must be careful to do this only when it is appropriate. For example, in code like the above, where there are two separate calls to error_handler() it would be preferable, if generating an error message, to be able to identify the exact cause of the error. If the file can't be access'd, the first structure would allow a message like "File exists but you can't access it" (assuming the file had already been successfully stat'd), while the second would have to say something like "Couldn't open file." It's important not to let the structure of the code determine the functionality of the program (aka "the tail wagging the dog" syndrome). The desired functionality should be predetermined before the code writing is done. -Dave Sill dsill@nswc-oas.arpa The opinions expressed above are those of the author and do not necessarily reflect those of the Department of Defense or the U.S. Navy.
cg@myrias.UUCP (Chris Gray) (07/13/87)
On the subject of 'while' loops, Doug Gwyn (gwyn@brl.arpa) suggests: > Perhaps an ideal programming language would make this something like: > until Get_Next_Character named `c' indicates No_More_Chars, > Do_Stuff_With `c' Actually, what (to me) is much better, so I've included it in a couple of compilers I've done is: while ch := getNextCharacter(); ch ~= EOF do doStuffWith(ch); od; Actually, in my latest language, I would write: while read(inputChannel; ch) do doStuffWith(ch); od; where 'read' is the usual language construct, but which can return a failure indicator where needed. -- Chris Gray Myrias Research, Edmonton +1 403 432 1616 {seismo!mnetor,ubc-vision,watmath,vax135}!alberta!myrias!cg
barmar@think.uucp (Barry Margolin) (07/14/87)
In article <1219@ius2.cs.cmu.edu> edw@ius2.cs.cmu.edu (Eddie Wyatt) writes: > I can think of no Unix system call that doesn't return -1 on error. >So I would say that it's a pretty good bet that "if (call(...) < 0)" and >"if (call(...) == -1)" will act the same in all cases. Though, one should >always consult the man pages for return values if in doubt. The first sentence is probably correct; however, it doesn't imply the second. I recently fixed a bug in some network code that assumed the two were equivalent. It was calling the function that translates a host name into a network address, expressed as an int. The man page didn't explicitly mention that many valid addresses are negative numbers, so the programmer assumed that a <0 test would be OK. Until I fixed it, this program was unable to communicate with any hosts on internet class B and C networks.
karl@haddock.ISC.COM (Karl Heuer) (07/15/87)
In article <5065@utcsri.UUCP> greg@utcsri.UUCP (Gregory Smith) writes: >In article <47000012@uiucuxe> mcdonald@uiucuxe.cso.uiuc.edu writes: >>>> The main advantage of this idiom is for "while" statements. The usual >>>> example is "while ((c = getchar()) != EOF) ...", which cannot be >>>If "c" is of type "char" this is still not written cleanly ... >>>To do this right you need an extra int temporary value. >But WHY DO YOU NEED char c IN THE FIRST PLACE??? As the person who posted the original idiom, I'll mention here that I intended the variable c to be type int; I left out the explicit declaration and the #include <stdio.h> because I assumed both were well-known. If I had known the subject was going to change like this, I would have included a footnote. >There is no reason to use char instead of int, except to save space in arrays >or structs. Simple auto variables should be declared as int. Not entirely true; consider "while ((c=getchar()) != EOF) write(1, &c, 1);". This is incorrect% for "int" as well as for "char", and the simplest way to make it work is to use one of each: "char c; int i; while ((i=getchar()) != \ EOF) { c=i; write(1, &c, 1); }". >The mnemonic effect of making the variable a 'char' because you are using it >to store a character is not really worth the trouble. Given the semantics of getchar(), I agree. However, I consider getchar() to be a botch; given proper error%% handling, getchar() *should* return a char. Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint % With "int c" it happens to work on a VAX. That doesn't make it right. %% This includes end-of-file, although it isn't an "error" in the usual sense.
devine@vianet.UUCP (Bob Devine) (07/15/87)
In article <8249@brl-adm.ARPA>, Leisner.Henr@Xerox.COM (marty) writes: > Also, it is bad practice to define NULL to be something memory model > dependent. A better practice is: > > #define NULL (char *) 0 > > This automatically takes care of the sizeof dependencies. This gets to > be a major issue when routines are being passed and/or return NULL. Don't do this. For x86 C Compiler memory models, (char*) might be 2, or 4 bytes depending on the model. BUT, there is often mixing of models and there are portability concerns beyond the immediate need. Just use 0 for NULL and cast it appropriately. Bob Devine
chips@usfvax2.UUCP (Chip Salzenberg) (07/16/87)
John Haugh <killer!jfh> wrote: > > if (access (file, 0) && (fd = open (file, 0)) >= 0) > good_stuff () > else > error_handler (); > Just a nit: I believe that the return value of access() will be zero if the file does exist; thus the test should be "if (access(...) == 0" etc. Keeping the world safe for debuggers, this is Chip Salzenberg signing off at WBUG, Bug Radio. -- Chip Salzenberg UUCP: "uunet!ateng!chip" ..or.. "chips@usfvax2.UUCP" A.T. Engineering, Tampa Fidonet: 147/40 "Use the Source, Luke!" My opinions do not necessarily agree with anything.
smith@COS.COM (Steve Smith) (07/16/87)
In article <2365@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes: >... Some of us are actually not permitted >to "do it right." > ... >"Yes, documentation is important, but so-and-so customer is screaming >for such-and-such a feature, so we can't schedule time to write anything >but code." Amen!! Gary's points are well taken. Besides managerial incompitence, there is another problem to deal with, and that is the (all kowtow) CUSTOMER. As a former employee of a Beltway Bandit (suburban Washington DC defense contractor) whose only desire is to remain nameless, I had to constantly deal with managers who swore up and down that "the Customer needs first rate doccumentation". Very nice. Except that "doccumentation" meant "doccumentation to MIL-STD-1679" AND NOTHING ELSE. For those of you who are mercifully unaware of this monstrosity, it was written in the early '70's to standardize the doccumentation of COBOL programs. The doccumentation has two sections, variables and code. All data structures, variables, and subroutines must be treated as globals. Variables are described by little pictures telling what the bits mean. Defining structures in terms of other structures is forbidden. There is no way to represent any form of concurrency or multitasking. This awful stuff must be written and approved by the Customer before you can do any coding at all (including proof-of-concept). Once it is approved, it cannot be changed. This may be OK for COBOL (I count COBOL as an unnatural act, anyway). For FORTRAN or C, it is just marginal. For C++, PASCAL, ADA, or any AI language, it is impossible. The effect of all this is to push the code away from a modular, object oriented design toward "structured spaghetti code". I don't care if a program doesn't use GOTOs, if it has hundreds of teeny little modules all calling each other with no apparent large scale structure, it's going to be hard to figure out. If you DO try to maintain some high level structure through "informal design doccuments" (specifically forbidden by most contracts) you confuse the bejesus out of everybody, because you now have two sets of contradictory doccumentation. The net effect of this is that Management and the Customer THINK that they are insuring good doccumentation, while what happens is the reverse. After any maintenence activity at all, any structure that might have existed is completely trashed. Faugh! Anybody who has ever done program maintenence knows what s/he needs. The exact format is unimportant.
mpl@sfsup.UUCP (M.P.Lindner) (07/17/87)
Right on! I can't tell you how many times I've seen this: while ((foo & 17) || (*((struct goo *) &x[3])->blah)(97)->bar & 11) n++; /* increment n */ Argh! Or how about a 200 line source file to look at, with no mention of where the entry points are (if there's no "main()"). static declarations can hepl this somewhat, but LET'S COMMENT!
cramer@kontron.UUCP (Clayton Cramer) (07/18/87)
> In article <2365@bunker.UUCP> garys@bunker.UUCP (Gary M. Samuelson) writes: > > >... Some of us are actually not permitted > >to "do it right." > > ... > >"Yes, documentation is important, but so-and-so customer is screaming > >for such-and-such a feature, so we can't schedule time to write anything > >but code." My least pleasant experience in this vein was sometime back -- I won't name any names -- but it's a painful example. A company I worked for had been run by some less than scrupulous sorts (or perhaps incompetents). The California division had been saying, "Yes, we have X people writing design documents for the new product. We're making great progress." The management team from corporate headquarters flies out to California, and finds out that basically, the steaming pile of badly written, pie-in-the-sky documentation for the product was so vague and meaningless as to be useless. The California management runs to the startup they've been putting together in the meantime (on company time, I understand), and me the peon ends up a supervisor. Corporate's management asks, "How long will it take to build the product?" I say, "Well, the current design documents really don't say anything. I figure in about six to nine months we should have a functional specification and design document. Then we'll get everyone to look it over, sign off, and start detail design and coding." They look at me as though I'm pulling a fast one on them. (Remember, they've already invested a year and half of several people's time writing a functional spec which is nonsense.) They say, "You will have to design and code in parallel." Can you blame them? They were already taken for a ride once -- and put out huge chunks of money, with no real return -- at least for the people paying the bills. They wanted proof the engineering department that hadn't jumped ship weren't just a bunch of Newport Beach slimebuckets ripping them off. It worked better than I would have guessed -- but I guess it's just my strong leadership and project management skills that made it work. :-) Clayton E. Cramer
rwhite@nu3b2.UUCP (Robert C. White Jr.) (07/25/87)
In article <840@mcgill-vision.UUCP>, mouse@mcgill-vision.UUCP (der Mouse) writes: > The difference becomes significant if you ever take the address of c. > For example, > > write(fd,&c,1); > > takes on an entirely different meaning. If c is a char, this is > portable (provided sizeof(char)==1 -- are there any machines on which > this is not true?), but if c is an int you get what you probably expect > on a little-endian and it breaks mysteriously on a big-endian. Oddly enough, by definition what you have used above is a valid construct on just about every machine I have ever seen. Since the low order byte of a word, and the low order word of a double-word are stored "above" the high-order portion you get: +--------+--------+ | low | high | +--------+--------+ ^ | This is the point indicated by the address-of operator and if char is the size of int, it does not matter. If char is a byte and int is a word, the low order bits of the int corrispond to the byte that would be union aligned of type char [etc]. By the definition your code fragment WILL work [i.e. produce the correct result] wether c is int or char. [which is why the spec. tells you to use int] On any machines which do not use this inter-position, the use of pointers is massaged in the memory model to produce the same net effect. If this arangement, or some adaptive work-around were not built into the language, the casting between types [i.e. assigning the value of a sufficiently small int to a char] would produce more logical shifting and adaptive work than the actual math/assignment. int x; char y; x = 13 ; y = x; would be unreasonably large. The only real problem comes down to potiner MATH, but as long as aray of int/char are used consistantly that wont matter either. If you dont believe me, get out your DDT and take a look. If I am wrong..... SUE ME! Robert. Disclaimer: My mind is so fragmented by random excursions into a wilderness of abstractions and incipient ideas that the practical purposes of the moment are often submerged in my consciousness and I don't know what I'm doing. [my employers certainly have no idea]
guy%gorodish@Sun.COM (Guy Harris) (07/27/87)
> Oddly enough, by definition what you have used above is a valid > construct on just about every machine I have ever seen. "valid", yes, in the sense that the C compiler won't reject it; it may give you a warning, but it won't reject it. However, that construct won't do what you want it to do on a big-endian machine; therefore, since the 3B2 is a big-endian machine, if the "3b2" in the name of the machine you're posting from indicates what type of machine it is, you mustn't have "seen" that machine. I took the code: main() { int c = 'a'; write(1, &c, 1); } and compiled it on a 3B2, ran it, and piped the output through "od -c" to see exactly what the output was. "od -c" printed: 0000000 \0 \0 0000001 (the second '\0' is an artifact of "od" working in 2-byte words). Note that it did NOT write an 'a', but wrote a '\0'. This is what was in the high-order byte of the (4-byte) quantity "c". > Since the low order byte of a word, and the low order word of a double-word > are stored "above" the high-order portion you get: > > +--------+--------+ > | low | high | > +--------+--------+ > ^ > | > This is the point indicated by the address-of operator Wrong. On big-endian machines, the *high*-order byte of a 2-byte or 4-byte quantity is the one whose address has the same bit-pattern as the address of the quantity itself. > On any machines which do not use this inter-position, the use > of pointers is massaged in the memory model to produce the same net > effect. Wrong. This is not done on any big-endian machine that I've worked with. > If this arangement, or some adaptive work-around were not > built into the language, the casting between types [i.e. assigning > the value of a sufficiently small int to a char] would produce more > logical shifting and adaptive work than the actual math/assignment. > int x; char y; x = 13 ; y = x; would be unreasonably large. Wrong. The code sequence for "x = 13; y = x" generated for a 68020 (another big-endian machine) by our compiler is: moveq #13,d1 /* 13 */ movl d1,a6@(-4) /* x = 13; "x" is at a6@(-4) */ movb a6@(-1),a6@(-5) /* y = x */ The "movb" merely picks up the appropriate byte of "x" and stuffs it into "y". Please note that the address of "x" is <contents of a6>-4, but the address of the appropriate byte of "x" is <contents of a6>-1, or <address of x>+3. No shifting or "adaptive work" (whatever THAT means) is required. Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
m5@bobkat.UUCP (Mike McNally ) (07/28/87)
In article <1126@nu3b2.UUCP> rwhite@nu3b2.UUCP (Robert C. White Jr.) writes: > > Oddly enough, by definition what you have used above is a valid >construct on just about every machine I have ever seen. Since the low >order byte of a word, and the low order word of a double-word are stored >"above" the high-order portion you get: > > +--------+--------+ > | low | high | > +--------+--------+ > ^ > | > This is the point indicated by the address-of operator >and if char is the size of int, it does not matter. If char is a byte >and int is a word, the low order bits of the int corrispond to the >byte that would be union aligned of type char [etc]. By the definition >your code fragment WILL work [i.e. produce the correct result] wether >c is int or char. [which is why the spec. tells you to use int] Sorry, Robert, but I'm going to have to sue you. On many machines this is very much untrue. Multi-byte primitive objects are stored +-------+-------+-------+ +-------+ | MSB | MSB-1 | MSB-2 | . . . | LSB | +-------+-------+-------+ +-------+ The C language does not specify the byte ordering used for multi-byte primitive data objects. > On any machines which do not use this inter-position, the use >of pointers is massaged in the memory model to produce the same net >effect. Not on any I've ever used. It's very simple and inexpensive to do casts: int i; char c; i = c; translates to (on a 680x0): MOV.B c, D0 EXT.W D0 ; Maybe followed by an ext.l if ints are 32 bits On *any* machine, something has to be done to set the value of the upper eight bits of the int, so there is no difference in code. > If this arangement, or some adaptive work-around were not >built into the language, the casting between types [i.e. assigning >the value of a sufficiently small int to a char] would produce more >logical shifting and adaptive work than the actual math/assignment. > int x; char y; x = 13 ; y = x; would be unreasonably large. Wrong wrong wrong. > The only real problem comes down to potiner MATH, but as long >as aray of int/char are used consistantly that wont matter either. > > If you dont believe me, get out your DDT and take a look. > If I am wrong..... SUE ME! > > >Robert. My lawyers will be in touch. -- 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!m5 (214) 238-7474
dg@wrs.UUCP (David Goodenough) (07/28/87)
In article <1126@nu3b2.UUCP> rwhite@nu3b2.UUCP (Robert C. White Jr.) writes: >In article <840@mcgill-vision.UUCP>, mouse@mcgill-vision.UUCP (der Mouse) writes: >> The difference becomes significant if you ever take the address of c. >> For example, >> >> write(fd,&c,1); >> >> takes on an entirely different meaning. If c is a char, this is >> portable (provided sizeof(char)==1 -- are there any machines on which >> this is not true?), but if c is an int you get what you probably expect >> on a little-endian and it breaks mysteriously on a big-endian. > > Oddly enough, by definition what you have used above is a valid >construct on just about every machine I have ever seen. ..... Since the NUXI problem exists (byte order between DEC & IBM) exactly one of the architectures will fail - in addition motorola 6800's will fail as will 6809's, both of these put high byte first, and the 68K will fail: in a 4 byte int the bytes are stored highest first, lowest last. On the rare occasions I use write(fd, &c, 1) (terminal output only) I *ALWAYS* declare c as a char - it is the only safe portable way to do it (If anyone disagrees I've got my asbestos suit ready :-) -- dg@wrs.UUCP - David Goodenough +---+ | +-+-+ +-+-+ | +---+
dave@murphy.UUCP (Dave Cornutt) (07/31/87)
In article <22250@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: > > When I see > > > > if (!p) > > > > I read it as > > > > if p is not valid then ... > > > > The (!p) syntax tells me that p is among the class of items that may be > > treated as boolean (under the C language conventions) and that we are > > testing whether it is false. This is not a matter of "saving characters"; > > it is a matter of classification. > > But what does it mean to say that a pointer is "false"? Pointers > themselves really aren't Boolean; there is a boolean predicate *on* a > pointer, namely the "is this pointer null" predicate. I was hoping you were going to go in a different direction here. I *do* like to use the constructs "if (p)" and "if (!p)"; I think that they are quite clear in what they mean, because I do think of pointers as being true or false: if a pointer points to something valid, then it's a "true" pointer because you can dereference it. If it is nil, then it's a "false" pointer because you can't dereference it. I think that this is quite clear; it's not just a matter of saving a couple of keystrokes, but that, in a large program, whenever you can say something clearly with less text, I think it adds to the readability of the program. The meaning of the syntax is quite clear in both K&R and the ANSI draft, and there should be no problems with portability among conforming compilers. Also, there should be no performance difference, since any halfway decent compiler should be able to recognize that "if (p)" and "if (p == NULL)" are the same and generate the same code. I do it purely because I *like* it. Then again, it isn't something I'm religious about. Lots of people that I respect write "if (p == NULL)" and I don't flame them for it. It's a free country, individual choice, etc... >You could view > the construct "p", used in a context that requires a Boolean > expression, as really meaning "is_non_null(p)", and "!p" as meaning > "!is_non_null(p)", or "is_null(p)". > > > When I see > > > > if (p != NULL) > > > > it tells me two rather different things. First of all it tells me that > > p is an item for which there are one or more coded values, among which > > is NULL, and that for all cases where p is not NULL, there is some action > > to be taken. > > All of which happen to be the case for pointers. Are you arguing > that "!p" is somehow better than "p != NULL"? This is a matter of > taste; if you do not view "!p" as shorthand for "is_null(p)", then "p > == NULL" makes more sense as a way of writing "is_null(p)", and many > good programmers do not view "!p" as such a shorthand. I *do* view !p as a shorthand for p == NULL. I could make an argument that "p == NULL" implies that p is an enumerated type, but I really don't believe that it does, so I won't. > > Secondly it tells me that the file that the statement is in > > includes stdio.h (or that the author of the code is a dweeb.) And that > > should tell me that the code in this file needs stdio.h, FOR I DO NOT > > CONSIDER IT GOOD PROGRAMMING PRACTICE TO INCLUDE INCLUDE FILES WHICH ARE > > NOT USED. A legitimate gripe; you run into the same if you use TRUE and FALSE. ANSI is going to take care of this by moving these types of things into a separate include file which just about everything will have to include. > Also, you didn't address the issue of > > if (!strcmp(str1, str2)) > This has always been one of my pet peeves. Even after years of C programming experience, I still find myself sometimes looking at this and reading it as "if str1 is not equal to str2..." I wrote a little function of my own which takes a string containing an operator in between the other two arguments, so it looks something like this: if (strcompare(str1,"==",str2)) An extremely simplistic parser decodes the operator, then strcmp is called to actually do the compare, and the routine figures out what to return depending on what the operator was; if the indicated operation was true, it returns nonzero, else zero. So, if (strcompare(str1,"<",str2)) and if (strcompare(str1,"!=",str2)) mean what they appear to mean. This isn't as efficient as defining macros to do the compare, but I think it reads a lot better. --- "I dare you to play this record" -- Ebn-Ozn Dave Cornutt, Gould Computer Systems, Ft. Lauderdale, FL [Ignore header, mail to these addresses] UUCP: ...!{sun,pur-ee,brl-bmd,seismo,bcopen,rb-dc1}!gould!dcornutt or ...!{ucf-cs,allegra,codas,hcx1}!novavax!gould!dcornutt ARPA: dcornutt@gswd-vms.arpa "The opinions expressed herein are not necessarily those of my employer, not necessarily mine, and probably not necessarilla
mpl@sfsup.UUCP (08/01/87)
In article <24246@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: > > Oddly enough, by definition what you have used above is a valid > > construct on just about every machine I have ever seen. > > "valid", yes, in the sense that the C compiler won't reject it; it may > give you a warning, but it won't reject it. [deleted stuff] > main() > { > int c = 'a'; > > write(1, &c, 1); > } So, the point is (if I remember the original article correctly) that one should use: main() { int i; char c; while ((i = getchar()) != EOF) { c = i; write(1, &c, 1); } } perhaps. However, why not use the (more efficient) code: main() { int i; while ((i = getchar()) != EOF) putchar(i); } I mean, I think this all stemmed from an early reply to the original article which claimed you *needed* an intermediate variable (i) no matter *what* you wanted to do with the character. For code like this "write" business an intermediate (c) might be useful, but you can still do without in a better fashion. How about: #include <sys/param.h> main() { int i; while ((i = getchar()) != EOF) write(1, lobyte(loword(i)), 1); } ANSI C promises to be better at providing standard macros like this as well. So.... Happy hacking! Mike
guy%gorodish@Sun.COM (Guy Harris) (08/02/87)
> > But what does it mean to say that a pointer is "false"? Pointers > > themselves really aren't Boolean; there is a boolean predicate *on* a > > pointer, namely the "is this pointer null" predicate. > > I was hoping you were going to go in a different direction here. I > *do* like to use the constructs "if (p)" and "if (!p)"; I think that > they are quite clear in what they mean, because I do think of pointers > as being true or false: if a pointer points to something valid, then > it's a "true" pointer because you can dereference it. If it is nil, > then it's a "false" pointer because you can't dereference it. Well, you can hope for lots of things; many of them won't come true, though, so such hope is largely a waste of energy. I don't personally care for "if (p)" and "if (!p)", but I don't strongly object to them either. However, defending them by saying that you're testing whether a pointer is "true" or "false" is not valid. You can say that, for pointers, they test whether the pointer does or does not point to something valid; however, pointers are NOT Booleans. They do not have a truth value as their value. There is a boolean *predicate* on pointers, namely the "is this pointer valid" predicate. If you want to say that "if (!p)" means "if (p is not valid)", fine, but this is very different from "p is false". > Then again, it isn't something I'm religious about. Lots of people that > I respect write "if (p == NULL)" and I don't flame them for it. It's > a free country, individual choice, etc... I don't flame them either. (Anyone who disagrees with that statement has serious problems with reading comprehension; such problems are, unfortunately, quite common on USENET.) > > > Secondly it tells me that the file that the statement is in > > > includes stdio.h (or that the author of the code is a dweeb.) ... > > A legitimate gripe; you run into the same if you use TRUE and FALSE. ... If you are replying to an article that includes quotes from other articles, please try to keep the replies to the various articles separate (or, even better, make your reply to those quotes a reply to the article in which they appeared). Thank you. Guy Harris {ihnp4, decvax, seismo, decwrl, ...}!sun!guy guy@sun.com
blm@cxsea.UUCP (Brian Matthews) (08/03/87)
In article <525@murphy.UUCP> dave@murphy.UUCP (Dave Cornutt) writes: |In article <22250@sun.uucp>, guy%gorodish@Sun.COM (Guy Harris) writes: |> Also, you didn't address the issue of |> if (!strcmp(str1, str2)) |This has always been one of my pet peeves. Even after years of C |programming experience, I still find myself sometimes looking at this |and reading it as "if str1 is not equal to str2..." I wrote a little |function of my own which takes a string containing an operator in |between the other two arguments, so it looks something like this: | |if (strcompare(str1,"==",str2)) I usually don't like mucking up code with lots of strange macros, but one I do find useful is something like: #define strrel(s,op,t) (strcmp((s), (t)) op 0) where op can be any of relational operators, ==, <, etc. So, your example would become: if (strrel(str1,==,str2)) This avoids the overhead of an additional procedure call, and parsing "==" (or "<", or whatever), but maintains the (in my opinion) increased readability. -- Brian L. Matthews "A man with one watch knows ...{mnetor,uw-beaver!ssc-vax}!cxsea!blm what time it is; a man with +1 206 251 6811 two watches isn't so sure." Computer X Inc. - a division of Motorola New Enterprises
DHowell.ElSegundo@Xerox.COM (08/04/87)
In article <525@murphy.UUCP>, Dave Cornutt <dave@murphy.uucp> writes, >...any halfway decent compiler should be able to >recognize that "if (p)" and "if (p == NULL)" are the same and generate >the same code. I'd hope that any decent compiler would recognize that they are completely opposite. The fact that it easy to make the above mistake is precisely the reason why one of these constructs is not clear. I leave it as an exercise to the reader to decide which one. Dan <DHowell.ElSegundo@Xerox.COM> DISCLAIMER: The opinions expressed above may not be anyone's opinions at all, but the random output of a bunch of monkeys on computer terminals.