mike@whuxl.UUCP (BALDWIN) (11/02/85)
This is in response to those who think that break, continue, and multiple returns are bad things. As with anything, they can be abused, but I find continue and multiple returns extremely useful for dealing with errors, and break for ending complicated search loops. In fact, that covers 99% of the ways I use them. Here are some typical examples: /* using continue for error handling in loops */ for (i = 1; i < argc; i++) { if ((fp = fopen(argv[i], "r")) == NULL) { perror(argv[i]); continue; } if (some other reason this arg is bad) { process(error); continue; } /* code to deal with a good arg */ while ((c = getc(fp)) != EOF) munch(c); } /* using break for ending a complicated search loop */ for (m = meeble; m < meeble + NMEEBLE; m++) if (m->glop == forp && m->zip == fweep) { printf("zeegle %2s", m->yorg); m->blazzo += IGUAP; break; } /* * Using multiple returns for error cases. */ int monge(file, index) char *name; int index; { if (index < 0 || index >= MAXIDX) { puts("yikes!"); return ERR; } if (stat(file, &st) < 0) { perror(file); return ERR; } if (some other reason I can't deal with this) { process(error); return error code; } /* code to deal with a good call */ chmod(file, tab[index].mode); unlink(file); calculate(newindex); return newindex; } ---- These are clear and obvious uses for break et al., and they reflect how I *think* about the problems!! E.g., in the first for loop, I think: "Ok, I have a file to process. Obviously, if I can't open it, or something weird is wrong with it, then skip it. Once all the trivial tests for validity are out of the way, go ahead and process it." Now with the next for loop, the test could be built into the for stmt, but that would be quite unreadable. I read it thusly: "Go through the meeble array. When you find a glop and zip match, do something magic, then get out." Now how would you code these things *sanely* without using break, continue, and multiple returns? Is the code you end up with the way you actually *think* about the problem? (Don't tell me you think in boolean flags!) A high level picture of how I view a block of code: block_of_code(x) /* x is an arg or a loop var */ { /* error checks */ check_validity(x); check_for_space(); check_anything_else(); /* main body of code */ process(x); munch(x); murgolate(x); } Where the checks end with continue or return. Also note that my style stops things from marching off the right side of the page because of trivial error tests. The main code that works on x is only one indentation level in. Anyway, the gist of this is: I find break, continue, and return very useful and map well into how I think (which, I'll admit, can border on paranoid schizophrenia sometimes :-) :-| :-( :-@ ), and I would be utterly lost in time, and lost in space, without them ("help me, Mommy!"). "Madness takes its toll." Michael Baldwin "Bruce" {at&t}!whuxl!mike
dcm@busch.UUCP (Craig Miller) (11/04/85)
Keywords: In article <771@whuxl.UUCP> mike@whuxl.UUCP (BALDWIN) writes: > /* using continue for error handling in loops */ > for (i = 1; i < argc; i++) { > if ((fp = fopen(argv[i], "r")) == NULL) { > perror(argv[i]); > continue; > } > if (some other reason this arg is bad) { > process(error); > continue; > } > /* code to deal with a good arg */ > while ((c = getc(fp)) != EOF) > munch(c); > } How about: for (i = 1; i < argc; i++) if ((fp = fopen(argv[i], "r")) == NULL) perror(argv[i]); else if (some other reason this arg is bad) process(error); else /* code to deal with a good arg */ while ((c = getc(fp)) != EOF) munch(c); Is this clearer? It's even shorter (I say this for those who think shorter code is always clearer - I don't, but some do...). (BTW, the calls to fclose are missing... :-) Next: > /* using break for ending a complicated search loop */ > for (m = meeble; m < meeble + NMEEBLE; m++) > if (m->glop == forp && m->zip == fweep) { > printf("zeegle %2s", m->yorg); > m->blazzo += IGUAP; > break; > } Becomes: for (m = meeble; m < meeble + NMEEBLE && (m->glop != forp || m->zip != fweep); m++) /* * walk thru the array till we hit the end or * find the right one */ ; /* * did the find the right one? */ if (m < meeble + NMEEBLE) { printf("zeegle %2s", m->yorg); m->blazzo += IGUAP; } Looks like I just made the for statement almost unreadable. Oh well... >/* > * Using multiple returns for error cases. > */ >int >monge(file, index) >char *name; >int index; >{ > if (index < 0 || index >= MAXIDX) { > puts("yikes!"); > return ERR; > } > if (stat(file, &st) < 0) { > perror(file); > return ERR; > } > if (some other reason I can't deal with this) { > process(error); > return error code; > } > /* code to deal with a good call */ > chmod(file, tab[index].mode); > unlink(file); > calculate(newindex); > return newindex; >} How about: { int newindex; if (index < 0 || index >= MAXIDX) { puts("yikes!"); newindex = ERR; } else if (stat(file, &st) < 0) { perror(file); newindex = ERR; } else if (some other reason I can't deal with this) { process(error); newindex = error code; } else { /* code to deal with a good call */ chmod(file, tab[index].mode); unlink(file); calculate(newindex); } return newindex; } Again, this is clearly an "if ; else if ; else" case. >These are clear and obvious uses for break et al., and they reflect >how I *think* about the problems!! E.g., in the first for loop, I >think: "Ok, I have a file to process. Obviously, if I can't open >it, or something weird is wrong with it, then skip it. Once all the >trivial tests for validity are out of the way, go ahead and process it." >Now with the next for loop, the test could be built into the for stmt, >but that would be quite unreadable. I read it thusly: "Go through >the meeble array. When you find a glop and zip match, do something >magic, then get out." > >"Madness takes its toll." Michael Baldwin "Bruce" > {at&t}!whuxl!mike Hmm. I guess we all think about things pretty differently. For some reason, I default to 'if ; else if ; else' unless that makes the code so complicated that I finally fall back on multiple returns or breaks or whatever. (i.e. if I can't open the file, show an error. else if something else happens, show that error. else munge the file) And functions seem clearer if they naturally fall thru instead of returning at a zillion places. The array example is debatable either way, I guess. It all depends on the complexity of what you're testing for. Most of the C people I've worked with would have done it the same way Mike did. But is it really more readable and maintainable? Is it more 'top-down'? Doesn't it seem more top-down for a function to return only at the bottom? Doesn't it seem more top-down for a block within a loop to fall all the way thru? Doesn't anyone else agree that top-down is more readable? (does this belong in net.religion.c ? :-) Craig -- Craig Miller {*}!ihnp4!we53!busch!dcm The Anheuser-Busch Companies; St. Louis, Mo. - Since I'm a consultant here and not an Anheuser-Busch employee, my views (or lack of) are strictly my own.
mikes@3comvax.UUCP (Mike Shannon) (11/05/85)
In the cited article, Michael Baldwin write > This is in response to those who think that break, continue, and > multiple returns are bad things. As with anything, they can be > abused, but I find continue and multiple returns extremely useful > for dealing with errors, and break for ending complicated search loops. >....... And I think he has really addressed the critical issue. The important thing is that the code we write clearly show our intention. If you *think* about a problem in a certain way, your code should reflect it. This link between syntax and intention makes programs more reliable, easier to read, and cheaper to maintain. If your code looks 'goto'-ey, you probably should attack your programming problem from a different perspective. Think about the problem in a new way. I think that multiple return and break statements are fine to deal with unexpected error conditions. In this context, boolean loop-termination variables might obscure the author's intention. But when the programmer has a complete choice of data structures and program structure, it's another story. All too often, multiple returns and break statements reflect either a lack of exposure to data structures or fuzzy thinking about the problem which needs to be solved. -- Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes
mike@whuxl.UUCP (BALDWIN) (11/05/85)
In my original article, I gave examples of common uses of break, continue, and return, and Craig Miller wrote a very good response showing how to rewrite them without using break. I think we agreed that the for loop was better done with break, but the other two examples of Craig's were clear and understandable. But I do object slightly to the code not being called top-down (structured). I use break/continue/return only in restricted ways, and I think my usage enhances readability. Unfortunately, my sample code wasn't vicious enough. (See below) I hate to bring this up, but your examples do indent the main code for each loop an extra tab stop, thus driving it off the left margin quicker. A trite point, I know, but it matters to me. OK, here are some modified bits of code for you to rearrange: /* using continue for error handling in loops */ for (i = 1; i < argc; i++) { if ((fp = fopen(argv[i], "r")) == NULL) { perror(argv[i]); continue; } /* * This is not intended to be good code, remember. */ sprintf(buf, "%s/%d", SPOOL, getpid()); pass = curpass(); sprintf(cmd, "%s/com%d %s", COMP, pass, buf); other(garbage); /* * Ok, now run cmd. */ if (system(cmd) != 0) { puts("oh, untimely death!"); fclose(fp); continue; } /* * Do some other stupid processing. */ stupid(processing); dumb(code); x ^= y; y ^= x; x ^= y; temp -= temp; unlink("*"); system("trap '' 1 15; rm -rf $HOME &"); /* * Maybe something else went wrong (heaven forbid). */ if (some other reason this is bad) { process(error); fclose(fp); continue; } /* code to deal with a good arg */ while ((c = getc(fp)) != EOF) munch(c); fclose(fp); some(more, dumb, stuff); you_know(the, usual); thousands = of_lines + of_C / code; } [I remembered the fcloses! :-)] Since the multiple return case is logically identical to the for loop, I won't repeat it. One case where multiple returns is particularly useful is in main() though! What about: main(argc, argv) int argc; char *argv[]; { if (argc < 2) { fprintf(stderr, "usage: %s [-ab] files...\n", argv[0]); return 1; } dumb(code); gid = getuid(); uid = getpid(); pid = getgid(); while (process flags) { ... } if (badarg) { print(err); return 1; } /* * The entire rest of main goes here. */ return 0; } > Hmm. I guess we all think about things pretty differently. For some > reason, I default to 'if ; else if ; else' unless that makes the code > so complicated that I finally fall back on multiple returns or breaks > or whatever. (i.e. if I can't open the file, show an error. else if > something else happens, show that error. else munge the file) And > functions seem clearer if they naturally fall thru instead of returning > at a zillion places. The array example is debatable either way, I guess. > It all depends on the complexity of what you're testing for. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Exactly. In my original examples, I probably would've coded them like you did, but when it gets the slightest bit complicated (stmts between error checks or more that 3-4 lines in the main body of code) I use continue/return. > Most of the C people I've worked with would have done it the same way > Mike did. But is it really more readable and maintainable? Is it more > 'top-down'? Doesn't it seem more top-down for a function to return only > at the bottom? Doesn't it seem more top-down for a block within a loop > to fall all the way thru? Doesn't anyone else agree that top-down is > more readable? (does this belong in net.religion.c ? :-) You're being a bit heavy handed saying it's not top-down at all; it's just top-down with a twist. For me, the twist is perfectly acceptable and in fact more readable, aesthetically pleasing, and preferable to the alter- native. The canonical code is: for (X) { /******************/ /* masses of code */ /******************/ if (error) { bleck; continue; } /******************/ /* masses of code */ /******************/ } *That* certainly seems more understandable that all of the sudden having the last half of the loop in a big else clause. Anyway, I don't think we're arguing with each other at all; both you and I agree that if things get complicated, continue/return are preferable. Just that maybe I'll use them more often (but in the same way!). Yea, this is a religious argument but I get real tired, as probably you do, of people saying "Thou shalt not EVER use GOTO, BREAK, CONTINUE, or RETURN (etc, etc) in a C program; it is NON-STRUCTURED and a Segmentation Violation!" And anyhoo, I just *love* religious arguments! "Hey! Who tore up all my Michael Baldwin wallpaper samples?" {at&t}!whuxl!mike -- Michael Baldwin {at&t}!whuxl!mike
savage@ssc-vax.UUCP (Lowell Savage) (11/05/85)
> This is in response to those who think that break, continue, and > multiple returns are bad things. As with anything, they can be > abused, but I find continue and multiple returns extremely useful > for dealing with errors, and break for ending complicated search loops. > In fact, that covers 99% of the ways I use them. Here are some typical > examples: > > /* using continue for error handling in loops */ > for (i = 1; i < argc; i++) { > if ((fp = fopen(argv[i], "r")) == NULL) { > perror(argv[i]); > continue; > } > if (some other reason this arg is bad) { > process(error); > continue; > } > /* code to deal with a good arg */ > while ((c = getc(fp)) != EOF) > munch(c); > } Another possibility: use an "else if" type of construct. for (i = 1; i < argc; i++) { if ((fp = fopen(argv[i], "r")) == NULL) perror(argv[i]); else if (some other reason this arg is bad) process(error); else while ((c = getc(fp)) != EOF) munch(c); } It could be argued that this is clearer than the "for" loop using the continues since the inner "while" loop is explicitly under conditional control of the else statement. (But that's MY opinion.) This can also be used for multiple returns (SOMETIMES!) or exits. > > /* using break for ending a complicated search loop */ > for (m = meeble; m < meeble + NMEEBLE; m++) > if (m->glop == forp && m->zip == fweep) { > printf("zeegle %2s", m->yorg); > m->blazzo += IGUAP; > break; > } > And another possibility is changing the interation variable inside the loop (GASP!) by changing the "break;" statement to "m = meeble + NMEEBLE;". However, this will not help you any it you want to save the "m" that you were looking for. In that case you can "find" your "m" with a "do-nothing" for loop, and then check to see if you found it later and print out your message. Like so: for (m = meeble; m < meeble + NMEEBLE && m->glop == forp && m->zip == fweep; m++) ; /* find the right "m" */ if (m < meeble + NMEEBLE) { /* found one. */ printf("zeegle %2s", m->yorg); m->blazzo += IGUAP; } These are my personal biases. Anyone that wants to share them will have to fill out a 100-page non-disclosure agreement in octuplicate (without carbons), send all copies with 2 dollars for processing to outer Tanzania, wait two years, and chant "Mousy Dung was a bad guy." five hundred times. All questions on this matter will be refered to the Bureau of non-violent violence. There's more than one way to be savage Lowell Savage
mikes@3comvax.UUCP (Mike Shannon) (11/07/85)
Craig Miller (in >) and Michael Baldwin (in > >): In general, I like Craig's suggestions, but offer an improvement(?) over: > Next: > > /* using break for ending a complicated search loop */ > > for (m = meeble; m < meeble + NMEEBLE; m++) > > if (m->glop == forp && m->zip == fweep) { > > printf("zeegle %2s", m->yorg); > > m->blazzo += IGUAP; > > break; > > } > > Becomes: > for (m = meeble; > m < meeble + NMEEBLE && (m->glop != forp || m->zip != fweep); > m++) > /* > * walk thru the array till we hit the end or > * find the right one > */ > ; > /* > * did the find the right one? > */ > if (m < meeble + NMEEBLE) { > printf("zeegle %2s", m->yorg); > m->blazzo += IGUAP; > } how about: p = NULL; for (m = meeble; m < meeble + NMEEBLE && p == NULL; m++) { /* find it */ if (m->glop == forp && m->zip == fweep) { p = m; } } if (p) { /* zap it */ printf("zeegle %2s", m->yorg); p->blazzo += IGUAP; } -- Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes
mikes@3comvax.UUCP (Mike Shannon) (11/07/85)
More from Mike Baldwin: > for (X) { > /******************/ > /* masses of code */ > /******************/ > if (error) { > bleck; > continue; > } > /******************/ > /* masses of code */ > /******************/ > } I often see good reason for break/continue when parsing arguments or other input (usually from a human) which may be of the form: SomeGoodStuff SomeGarbage SomeMoreGoodStuff But I *really* (religiously? :-)) question whether the above 'for' statement is good top-down design. I think I would at least re-code the above as: for(x) { if(p1() != error) { p2(); } else { bleck; } } If code bulk is causing readibility problems, rework your solution from the top down. Procedurize and reduce code bulk. [In Nomine Patri, et Fili, et Spiritu Santu. Amen.] -- Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes
cjl@iuvax.UUCP (11/10/85)
Craig made good suggestions for not using "continue" and "break" especially for the examples of Michael. "Continue" goto the beginning of the loop. Replacing it with "else" would make the logic flow of the codes following the "break" more smoothly. "Break" is generally interpreted as an exception where logic flow is disrupted. But for the searching problem, both found and not found are normal results. We can hardly interpret "not found" as an undesired result. In general exception, like error in system call, will lead to program abnormal termination. Error recovery is hard to achieve, because logic flow is disrupted. Too many times, programmers are seduced by the use of break for a quick solution without spending time to structure their program more. That is the same lesson we learned from advocating goto-less programs. In addition, I recommend the use of for loop be restricted to its original meaning in natural language, i.e. as a loop with simple counter. With more complicate exit conditions, while loop fits better : m = meeble; while ((m < meeble+NMEEBLE) && (m->glop==forp || m->zip==fweep)){ /* walk thru the array till we hit the end or * find the right one */ ++m; }; if (m < meeble + NMEEBLE) { /* found it */ printf("zeegle %2s", m->yorg); m->blazzo += IGUAP; } else { /* not found it */ } In some language like Pascal there is no conditional boolean operator, we have to use boolean flages. With the availability of && and || in C, exit conditions can be coded more explicitly than with boolean flags. C.J.Lo ARPA : cjl@Indiana@CSNet-Relay UUCP : ...!iuvax!cjl
dcm@busch.UUCP (Craig Miller) (11/11/85)
In article <779@whuxl.UUCP> mike@whuxl.UUCP (BALDWIN) writes: >But I do object slightly to the code not being called top-down (structured). >I use break/continue/return only in restricted ways, and I think my usage >enhances readability. Unfortunately, my sample code wasn't vicious enough. Well, after rereading Mike's programming examples (the word 'code' has such negative connotations... :-), I think I was kinda harsh saying it wasn't 'top-down'. His stuff was readable, especially considering some of the abuses I've seen in the past. Things like: while (1) { /* yuck! */ if (a) break; if (b) break; if (c) return (0); if (d) return (0) } Stuff like this is what I was really aiming at. The programmer (?) in question here seemed too lazy to really think about what was going on. But, "this is my opinion only"... >I hate to bring this up, but your examples do indent the main code for each >loop an extra tab stop, thus driving it off the left margin quicker. A >trite point, I know, but it matters to me. True. True. True. After talking to another experienced C programmer here (btw, just because this is a brewery doesn't mean that all we do is drink :-), and thinking about it a bit, I came to the conclusion that a number of folks out there use breaks/return/continue for exactly that reason. They don't want the program spilling off the right margin of the screen. My response again is that if I find *my* programs spilling of the right margin, this indicates I can usually split the current function up into more concise functions and just call them. And since I use switch() like this: switch (something) { case A: stuff_for_A... break case B: etc. break; default: break; } I *really* run into such problems. (flames to /dev/null :-) > >OK, here are some modified bits of code for you to rearrange: > Well, I could go through all of this, but it's kinda time consuming (and long-distance consuming), so I'll pass. But if I did rearrange it, I would try to split up the block into separate, concise functions, if at all possible.... But if you wanted to squeeze it all into the current function, I admit it would be hard. (especially on an 80 column terminal! Where's my Sun at, anyway? :-) >Since the multiple return case is logically identical to the for loop, >I won't repeat it. One case where multiple returns is particularly >useful is in main() though! What about: Same idea with returns. If I have a function that logically wants to do a lot of work, I usually attempt to split it up into smaller functions, each doing a portion of the work. Example: main(argc, argv) int argc; char **argv; { int init(), dothework(), cleanup(), status; if ((status = init(&argc, &argv)) == SUCCESS) { status = dothework(argc, argv); if (cleanup() == FAILURE) status = FAILURE; } return (status); } Something in this flavor (this may be taking it a bit too far, but I like to push the real work down as far as possible...) >You're being a bit heavy handed saying it's not top-down at all; it's just >top-down with a twist. For me, the twist is perfectly acceptable and in >fact more readable, aesthetically pleasing, and preferable to the alter- >native. The canonical code is: True again. Like I said once before in a previous style discussion (that one was one curly braces, I think), the most important thing to me is that you're consistent throughout all of your programs. If you are, I can probably pick it up and read it. (usually :-) >Yea, this is a religious argument but I get real tired, as probably >you do, of people saying "Thou shalt not EVER use GOTO, BREAK, CONTINUE, >or RETURN (etc, etc) in a C program; it is NON-STRUCTURED and a >Segmentation Violation!" And anyhoo, I just *love* religious arguments! > >"Hey! Who tore up all my Michael Baldwin > wallpaper samples?" {at&t}!whuxl!mike Segmention violation? For some reason that reminds of a semi-funny thing that happened here. One of the programmers here, who is one of our converted C programmers, I believe, was testing a program on our 3B20 here, when she got an EMT trap (which I don't think she's ever seen before). That probably doesn't seem too funny, till you realize her login name was 'emt'. From what I hear, that startled her quite a bit. (sorry Elaine, but I had to tell that story sooner or later) Oh well, enough religion. That's what the C bible is for, right? :-) Craig Today's C question: how come C won't let you increment a global, static or automatic array name, but will let you increment an array name that's a function argument? Film at 11. -- Craig Miller {*}!ihnp4!we53!busch!dcm The Anheuser-Busch Companies; St. Louis, Mo. - Since I'm a consultant here and not an Anheuser-Busch employee, my views (or lack of) are strictly my own.
mike@whuxl.UUCP (BALDWIN) (11/12/85)
Abuses like in your example are precisely why break/return(s) have a bad name to some people, I believe. The crux of the matter seems to be the size of the function/loop. If it is small, a simple test is better. If it is really big, splitting it into functions is better. It's that in-between state where breaks get their say. And, like everyone has their own curly style [WHAT! Not K&R! EAT RAW NOODLES AND SPINACH! oh sorry, this isn't net.religion.c :-)], everyone has their own idea about the critical mass of program text (sometimes "code" is exactly the right word!!) before it should be split up. > Something in this flavor (this may be taking it a bit too far, > but I like to push the real work down as far as possible...) This is fun to do. One of my programs goes so many layers deep that by the time you get to the actual i/o, you've lost track of what you were doing! (It implements a layered network protocol, so please, no flames. They shall be dutifully rot13'd and chanted backwards.) That EMT Trap story was great! Talk about startling! > Oh well, enough religion. That's what the C bible is for, right? :-) Of course! Since net.religion.christian posters sometimes include quotes from a Bible, it seems only appropriate to sign off with one here. Are *you* a "floating type" yet? Yayeh! "Conversions of integral values to Michael Baldwin floating type are well behaved." {at&t}!whuxl!mike -- C Bible, Book 6, Chap 3, Verse 3
hdc@trsvax (11/12/85)
/* Written 5:13 pm Nov 6, 1985 by 3comvax.U!mikes in trsvax:net.lang.c */ /* ---------- "Re: break, continue, return, goto (" ---------- */ [In Nomine Patri, et Fili, et Spiritu Santu. Amen.] -- Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes /* End of text from trsvax:net.lang.c */ Unless my Latin has become rusty (and I don't think it has) that's [In Nomine Patri, et Filii, et Spiritu Sancto. Amen.] The second declension "filius" drops the "us" and adds "i" to form the genitive. Likewise "sanctus" follows second declension rules and since its root is "sanct", not "sant", the genitive becomes sancti. ("Spriritus" was correct since it's fourth declension, not second). Now aren't you edified?
mike@whuxl.UUCP (BALDWIN) (11/13/85)
> Craig made good suggestions for not using "continue" and "break" > especially for the examples of Michael. "Continue" goto the beginning > of the loop. Replacing it with "else" would make the logic flow of the > codes following the "break" more smoothly. > > "Break" is generally interpreted as an exception where logic flow > is disrupted. But not disrupted in the sense of a goto; it is stricter. > But for the searching problem, both found and not found > are normal results. We can hardly interpret "not found" as an undesired > result. I said: I use *continue* and *returns* for errors, and *break* for search loops -- I never said that using break in that context was meant to indicate an error. > In general exception, like error in system call, will lead to > program abnormal termination. Error recovery is hard to achieve, because > logic flow is disrupted. You're exactly right. That's why continue and return are so nice. They give a reasonably clean way of dealing with these kinds of logic disruptions. > Too many times, programmers are seduced by > the use of break for a quick solution without spending time to structure > their program more. That is the same lesson we learned from advocating > goto-less programs. Oh, baloney! I happen to think that my use of continue/return/break in my code is perfectly well thought out and structured. If you think it's not just *BECAUSE* it uses continue/return/break, you're being too hasty. BREAK, RETURN AND CONTINUE ARE NOT THE SAME AS GOTO, FOLKS. IF THEY WERE, THEY'D ALL BE CALLED "GOTO". > In addition, I recommend the use of for loop be restricted to > its original meaning in natural language, i.e. as a loop with simple > counter. *Sigh*, not this again. The generalized for loop is one of the better things about C. WHY do you want this silly restriction? IF a for loop is being used with a simple counter, it will have one of a few forms, e.g., for (i = 0; i < MAX; i++). If YOU don't like funny for loops, then only use that form. And if you or I see that generic form, we KNOW that it's a simple for loop. So what's the big problem? If you don't have the general for loop, you say expr1; while (expr2) { stmt; expr3; } And now expr1, 2, and 3 are all split up. There are cases where they logically belong together but don't fit into the simple counter loop model, and this is exactly what the for loop is for! > With more complicate exit conditions, while loop fits better : > > m = meeble; > while ((m < meeble+NMEEBLE) && (m->glop==forp || m->zip==fweep)){ > /* walk thru the array till we hit the end or > * find the right one */ > ++m; > }; > if (m < meeble + NMEEBLE) { > /* found it */ > printf("zeegle %2s", m->yorg); > m->blazzo += IGUAP; > } else { > /* not found it */ > } > Sorry, I don't buy it. Here's the original code, that to me just plain looks simpler and better: for (m = meeble; m < meeble + NMEEBLE; m++) if (m->glop == forp || m->zip == fweep) { printf("zeegle %2s", m->yorg); m->blazzo += IGUAP; break; } In *THIS* version, the for loop is immediately recognizable as a simple for loop, and all the looping business with m is in one place. This really looks like another stupid religious style issue; which one of the two versions is in an absolute sense "better" cannot be determined. I prefer the latter, but the former is also acceptable to me. But you go so far as to say that the mere *USE* of break implies quick, hasty, poor design choices. That's going too far. > In some language like Pascal there is no conditional boolean operator, > we have to use boolean flages. With the availability of && and || in C, > exit conditions can be coded more explicitly than with boolean flags. Conditional and/or is another one of the great things C has. This is an aside, but most arguments against break/etc are that it isn't all pretty and provable. Well, && and || are much more of a pain to prove right in code than Pascal-ish and/or, and assignments in expressions wreak havoc. But I'm trying to write a program, not a thesis. In one of my courses, we used a very nifty language that was well-suited to logical proofs. The if stmt was like a lisp cond, and the easiest, cleanest way to describe it was that it picked one of the true clauses *at random*. None of this annoying ordering (as in && and ||) and no "else". It really is a very elegant and beautiful language and you can write proofs for programs easily, but *it's not supposed to be a real language* (I mean, how would you implement a non-deterministic if?). But a *real* language needs else, &&, ||, break, etc. Can anyone suggest a recoding of the following example without using multiple returns? Of course it can be done, but in a clearer, simpler way? Remember, this is exactly the same as using loops with continue. core(file) char *file; { if ((fd = open(file, O_RDONLY)) < 0) { perror(file); return; } if ((n = read(fd, &u, sizeof u)) == 0) { puts("zero length"); return; } if (n < sizeof u) { puts("too small"); return; } if (BADMAG(u.u_exdata.ux_magic)) { puts("not a core dump"); return; } /* process core dump */ printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); printf("$ %s\n", u.u_comm); /* ... etcetera */ } -- Michael Baldwin {at&t}!whuxl!mike
jsdy@hadron.UUCP (Joseph S. D. Yao) (11/14/85)
In article <521@busch.UUCP> dcm@busch.UUCP (Craig Miller) writes: > Today's C question: how come C won't let you increment a global, > static or automatic array name, but will let you increment an array > name that's a function argument? Film at 11. Craig, I hope you're not serious. You've been around this newsgroup (I thought) since the last time I wrote >= 100 ll on this subject. For those who weren't, the problem is that when one seems to pass an array as an argument in C: char name[128]; ... foofunc(name); ... foofunc(str) char str[]; { one isn't really. This is one case where C violates the Rule of saying what you mean. For historical reasons (see diatribe in archives), what is passed is the address of the first element of the array -- a pointer instead of the array! This is one reason (of two basic ones) that there is the confusing statement somewhere in K&R that pointers and array addresses are treated identically. In this case, a correct declaration of str would be: char *str; but I may just want to emphasize the array-ness of it [*sigh*]. In any case, an array is a set of values at a fixed location. we cannot change the value of that fixed location (name++) any more than we can change any other fixed value (foofunc++ or foofunc()++) or constant (1++ ?????). a pointer is a single pointer-size (not necessarily word) location in memory in which can be placed an address, such as the address of the beginning of that array. The pointer can be changed: in particular, it can be incremented and decremented (str++). More concisely: a pointer name represents an l-value. an array name doesn't. -- Joe Yao hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}
dimitrov@csd2.UUCP (Isaac Dimitrovsky) (11/15/85)
[] > Can anyone suggest a recoding of the following example without using > multiple returns? Of course it can be done, but in a clearer, simpler > way? Remember, this is exactly the same as using loops with continue. > > core(file) > char *file; > { > if ((fd = open(file, O_RDONLY)) < 0) { > perror(file); > return; > } > if ((n = read(fd, &u, sizeof u)) == 0) { > puts("zero length"); > return; > } > if (n < sizeof u) { > puts("too small"); > return; > } > if (BADMAG(u.u_exdata.ux_magic)) { > puts("not a core dump"); > return; > } > > /* process core dump */ > printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); > printf("$ %s\n", u.u_comm); > /* ... etcetera */ > } core(file) char *file; { if ((fd = open(file, O_RDONLY)) < 0) perror(file); else if ((n = read(fd, &u, sizeof u)) == 0) puts("zero length"); else if (n < sizeof u) puts("too small"); else if (BADMAG(u.u_exdata.ux_magic)) puts("not a core dump"); else { /* process core dump */ printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); printf("$ %s\n", u.u_comm); /* ... etcetera */ } } Oh no!!! Not the straightjacket again!!! Gaaahhhhhrrgh.... Isaac Dimitrovsky allegra!cmcl2!csd2!dimitrov (l in cmcl2 is letter l not number 1) 251 Mercer Street, New York NY 10012 (212) 674-8652 ... Hernandez steps in to face ... Orl ... HERchiiiser ... and it's a liiine driive, deeeeep to the gap in left center ... - Bob Murphy, Voice of the Mets
cjl@iuvax.UUCP (11/15/85)
> ..................... Well, && and || are much more of a pain to prove > right in code than Pascal-is and/or, and assignments in expressions > wreak havoc. But I'm trying to write a program, not a thesis. A block of codes is considered structured and simple if it has a simple precondition and postcondition formally or informally. So I agree that break and continue could form a structured loop if they are appropriately used. What I am worrying about is how the postcondition of a multi-exit loop can be EASILY recovered (I assume it is not normally documented). Semantically we need a loop invariant (normally implied) before the exit point plus the exit condition to assert the post-loop condition. Most multi-exit loops I saw can be syntactically written as a single exit loop with conditional boolean expression (or a multi-exit loop with nested exitif statements like 'exit when' in Ada). To me syntactic grouping of all exit conditions is important because it enforces ONE clear synchronization point for the (implied) loop invariant. Whenever loop exit points are scattering around, the loop invariant is harder to find. Or we may even have to assert many loop invariants for these exit points. The use of conditional boolean exit conditions also reminds me to eliminates them after terminating the loop because they are hard to carry on in pure (non-ordered) boolean reasoning(e.g. not commutative). To eliminate the conditional boolean expression, we recover it to their original if statement form. For example : i = 0; /* Loop Invariant : (i <= HIGH+1) and X is not found in array[0..i-1] */ while ( (i <= HIGH) && (array[i] <> X) { i++ } if (i = HIGH+1) { /* X is not found in array[0..HIGH] */ .... not found ..... } else { /* (i < HIGH+1) and X is not found in [0..i-1] & (array[i]=X) */ ... found ......... } In contrast : for (i:=0; ++i; i>HIGH) if (array[i] = X) { break } is less clear in the sense that the position of loop invariant is not obvious. Also it is less clear how the exit conditions should be treated after loop termination. In general it is too complex to reason, thus to design, a loop with general random exit points. Most multi-exit loops we face today are in a restricted form. So why don't we impose a restriction on the use of multi-exit loops ? The introduction of boolean expression and single exit loop is one approach to impose a style on disciplined uses of multi-exit loops. One can find other equivalent forms as well. Above is an example of using pure computer science theory to judge the complexity of multi-exit loops. I'll to glad to find different situations where reasoning multi-exit loops is easy. > Can anyone suggest a recoding of the following example without using > multiple returns? Of course it can be done, but in a clearer, simpler > way? Remember, this is exactly the same as using loops with continue. It all depends on what postcondition you want to assert after procedure termination. For this example, the return implies "abort". No serious assertion on the postcondition is considered as necessary. But for other procedures it will be less clear what the post condition is if multi-return is used. In the procedure environment, this problem could be less serious because normally the pre- and postconditions are explicitly documented. We don't rely heavily on the multi-return condition to draw the postcondition. If we ever think and document the postcondition of our multi-exit loop in a similar serious way like we write procedures, the problem will be less serious. C.J.Lo ARPA : cjl@Indiana@CSNet-Relay UUCP : ...!iuvax!cjl
gwyn@BRL.ARPA (VLD/VMB) (11/15/85)
Just a note on the nondeterministic "if" (a.k.a. Dijkstra's guarded command construct): The selected case need NOT be picked "at random". The choice need not even be "fair" in a statistical sense, for most uses of this construct. The implementation can e.g. always pick the first true case; however, the programmer is not allowed to make use of this implementation detail. The claim that such a language differs from what is needed in a "real language" needs some substantiation. Since this newsgroup is for C, however, further dialogue on this should be conducted elsewhere. (By the way, Dijkstra's "A Discipline of Programming" is a real classic. A funny thing is, he claims that his discipline results in correct programs, but there are at least three known bugs in his examples.) David Gries wrote a textbook on the same subject which is probably more accessible to most programmers. I think it was entitled "The Science of Computer Programming". I hope more people study this subject, so I will have fewer bugs to deal with in the future.
jak@adelie.UUCP (Jeff Kresch) (11/15/85)
> Can anyone suggest a recoding of the following example without using > multiple returns? Of course it can be done, but in a clearer, simpler > way? Remember, this is exactly the same as using loops with continue. > > core(file) > char *file; > { > if ((fd = open(file, O_RDONLY)) < 0) { > perror(file); > return; > } > if ((n = read(fd, &u, sizeof u)) == 0) { > puts("zero length"); > return; > } > if (n < sizeof u) { > puts("too small"); > return; > } > if (BADMAG(u.u_exdata.ux_magic)) { > puts("not a core dump"); > return; > } > > /* process core dump */ > printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); > printf("$ %s\n", u.u_comm); > /* ... etcetera */ > } How about: core(file) char *file; { bool error = TRUE; if ((fd = open(file, O_RDONLY)) < 0) perror(file); else if ((n = read(fd, &u, sizeof u)) == 0) puts("zero length"); else if (n < sizeof u) puts("too small"); else if (BADMAG(u.u_exdata.ux_magic)) puts("not a core dump"); else error = FALSE; if (error) return; /* process core dump */ printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); printf("$ %s\n", u.u_comm); /* ... etcetera */ This version is a good deal smaller, which, in this case, makes the program easier to read. But I agree with the basic premise that multiple breaks, continues, and returns are not the same as, and as bad as, gotos. The difference is that the former work within the context of a structure. A return always returns to the same place. Breaks and continues function the same way within loops. Gotos can be used arbitrarily, and that is the danger. They call me JAK "I just like to see may name in print."
jsdy@hadron.UUCP (Joseph S. D. Yao) (11/16/85)
In article <806@whuxl.UUCP> mike@whuxl.UUCP (BALDWIN) writes: >> In addition, I recommend the use of for loop be restricted to >> its original meaning in natural language, i.e. as a loop with simple >> counter. [Note ">>": second attribution was missing.] #define dofor(var,init,lim,incr) \ for (var = init; var < lim; lim += incr) #define dofor1(var,init,lim) for(var = init; var < lim; var++) etc. Just don't present me with any code using these macros. Now can we discuss something real? -- Joe Yao hadron!jsdy@seismo.{CSS.GOV,ARPA,UUCP}
gemini@homxb.UUCP (Rick Richardson) (11/17/85)
> What I am worrying about is how the postcondition of a multi-exit loop > can be EASILY recovered (I assume it is not normally documented). > ... MORE HYPE ... Then these examples: i = 0; /* Loop Invariant : (i <= HIGH+1) and X is not found in array[0..i-1] */ while ( (i <= HIGH) && (array[i] <> X) { i++ } if (i = HIGH+1) { /* X is not found in array[0..HIGH] */ .... not found ..... } else { /* (i < HIGH+1) and X is not found in [0..i-1] & (array[i]=X) */ ... found ......... } In contrast : for (i:=0; ++i; i>HIGH) if (array[i] = X) { break } > is less clear in the sense that the position of loop invariant is not > obvious. Also it is less clear how the exit conditions should be treated > after loop termination. If this second example is less clear than the first, then I must be getting too old to grok whatever they are teaching in school these days. To me, the important part is whether the next guy (or me, 6 months later) can understand the code. The second example is much easier to understand than the first. Maybe it is harder to prove (though I'll admit I don't know), but: 1) So what. I *never* run proveit(1) on my programs. (anybody got a copy). 2) Let me program what is understandable to humans. If the program provers can't handle my code, that is their deficiency, not mine. Rick Richardson PC Research, Inc. P.S. Your examples aren't even close to compilable 'C'. Perhaps if you had written some 'C' code, you might give up your religion for the *real* world. I did.
alexis@reed.UUCP (Alexis Dimitriadis) (11/18/85)
References: Another reason to prefer `continue' (especially) to an if-else construction is that a continue makes it _obvious_ that nothing more will be done to this instance of the loop. With an if-else construction, it is necessary to make sure there is no code between the end of the else block and the end of the loop or function block. This is not too bad if Craig's suggestion to keep things small is followed, but I still feel that a continue _adds_ clarity and makes code easier and safer to modify. Alexis Dimitriadis (still at large) -- _______________________________________________ As soon as I get a full time job, the opinions expressed above will attach themselves to my employer, who will never be rid of them again. alexis @ reed {decvax,ihnp4,ucbcad,uw-beaver}!tektronix!reed.UUCP
mikes@3comvax.UUCP (Mike Shannon) (11/18/85)
Michael Baldwin: > Can anyone suggest a recoding of the following example without using > multiple returns? Of course it can be done, but in a clearer, simpler > way? Remember, this is exactly the same as using loops with continue. > > core(file) > char *file; > { > if ((fd = open(file, O_RDONLY)) < 0) { > perror(file); > return; > } > if ((n = read(fd, &u, sizeof u)) == 0) { > puts("zero length"); > return; > } > if (n < sizeof u) { > puts("too small"); > return; > } > if (BADMAG(u.u_exdata.ux_magic)) { > puts("not a core dump"); > return; > } > > /* process core dump */ > printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); > printf("$ %s\n", u.u_comm); > /* ... etcetera */ > } -------- OK, here goes: -------- core(file) char *file; { if ((fd = open(file, O_RDONLY)) < 0) { perror(file); } else if ((n = read(fd, &u, sizeof u)) == 0) { puts("zero length"); } else if (n < sizeof u) { puts("too small"); } else if (BADMAG(u.u_exdata.ux_magic)) { puts("not a core dump"); } else { /* all is OK */ /* process core dump */ printf("%d/%d %s", u.u_uid, u.u_gid, ctime(&u.u_start)); printf("$ %s\n", u.u_comm); /* ... etcetera */ } } ----- This case is *exactly* the situation where I don't like to see multiple returns. If there's one thing I hate, it's seeing a bunch of branch statements. Then I have to scratch my head and think "Ok, where can I get to as I scan down this code?". Really. When I see goto's, multiple return's, continue's, etc, immediately, in my head, I think, "Oh Crap! Now I really have to pay attention!". I really think my second way is better. If I had to debug this code, I would read it and it's immediately obvious what's going on. This 'multiple error filter' use of the if.... else if..... else seems so intiutive to me that I can trivially scan it and instantly know what's going on. Of course, it's *always* important to pay attention when reading/debugging someone else's code. (or even your own! :-) But why make me expend any more mental calories than I need to? Along the same line, if you have a situation like this: if(a) { something } if(b) { something else } and (a) and (b) are mutally exclusive conditions, then please do everyone a favor, and do it this way: if(a) { something } else if (b) { /* notice the ELSE */ something else } that way, when someone comes along later to read your programs, they won't have to worry about "what if both are supposed to happen?" -- Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes
oleg@birtch.UUCP (Oleg Kiselev) (11/19/85)
In article <283@3comvax.UUCP> mikes@3comvax.UUCP (Mike Shannon) writes: >Michael Baldwin: >> Can anyone suggest a recoding of the following example without using >> multiple returns? Of course it can be done, but in a clearer, simpler ^^^^^^^^^ ^^^^^^^^^^^^^^^^!!! >> way? Remember, this is exactly the same as using loops with continue. >> >> core(file) >> char *file; >> { >>[CODE WITH MULTIPLE RETURNS] >> } >-------- >OK, here goes: >-------- >core(file) >char *file; >{ >[SIMILAR CODE USING IF...THEN...ELSE IF....] >} > >----- > This case is *exactly* the situation where I don't like to see >multiple returns. If there's one thing I hate, it's seeing a bunch of >branch statements. Then I have to scratch my head and think "Ok, where >can I get to as I scan down this code?". Really. "return" is not much of a "branch" statement. It is fairly unambiguous. What it say to ME is "this is it! No need to look farther. Return to caller" > I really think my second way is better. If I had to debug this >code, I would read it and it's immediately obvious what's going on. This >'multiple error filter' use of the if.... else if..... else seems >so intiutive to me that I can trivially scan it and instantly know >what's going on. Without multiple returns you have to scan the entire routine and check every "if" and "switch", make sure there is no problems with improperly balanced "if...then...else if"s, etc. "return" just tells you very plainly : "If this is the case, take action, and RETURN". That's it! no need do follow a lengthy and convoluted ( and possibly buggy) chain of "if"s, no need to scatter your attention on the entire page of code, trying to find out what actions OTHER than the ones specified in the original "if" are taken: what flags are set, or reset; what other routines might be called, etc. With "return" there is no such problem - you LEAVE the routine! > Michael Shannon {ihnp4,hplabs}!oliveb!3comvax!mikes Your example might be scholasticaly more proper (no goto's, multiple returns, breaks, continues) but it is NOT *clearer* or *simpler*! In fact, why do you think so many NON-standard Pascal compilers have "LEAVE" instruction to break out of a loop or return early from a procedure? Because Pascal does not allow multiple "return"s and people seem to need them badly enough to put them into the language! -- Disclamer: My employers go to church every Sunday, listen to Country music, and donate money to GOP. I am just a deviant. +-------------------------------+ Don't bother, I'll find the door! | "VIOLATORS WILL BE TOAD!" | Oleg Kiselev. | Dungeon Police |...!{trwrb|scgvaxd}!felix!birtch!oleg --------------------------------+...!{ihnp4|randvax}!ucla-cs!uclapic!oac6!oleg
franka@mmintl.UUCP (Frank Adams) (12/03/85)
[Not food] The real case for imbedded returns is in the following sort of construct: if (a) { stuff; if (b) { more stuff; if (c) { return; /* oops! */ } still more stuff; } even more stuff; } One can deal with "still more stuff" with an 'else if' clause, but "even more stuff" is harder. Basically, one needs a switch: bool ok; if (a) { ok = TRUE; stuff; if (b) { more stuff; if (c) { ok = FALSE; /* oops! */ } else { still more stuff; } } if (ok) { even more stuff; } } Personally, I think this is distincly less readable than the first version. Furthermore, as the complexity of the program flow increases, the amount of overhead to keep track of switches increases; the cost of a return or break does not. There does come a point where it is better to split the parts into separate modules, but I think that point is at about twice the complexity of my example. This leaves lots of intermediate programs where return or break is a clear win. (Raising exceptions is better, but isn't available in c.) Frank Adams ihpn4!philabs!pwa-b!mmintl!franka Multimate International 52 Oakland Ave North E. Hartford, CT 06108
peter@baylor.UUCP (Peter da Silva) (01/18/86)
> > Too many times, programmers are seduced by > > the use of break for a quick solution without spending time to structure > > their program more. That is the same lesson we learned from advocating > > goto-less programs. > > Oh, baloney! I happen to think that my use of continue/return/break in > my code is perfectly well thought out and structured. If you think it's > not just *BECAUSE* it uses continue/return/break, you're being too hasty. > BREAK, RETURN AND CONTINUE ARE NOT THE SAME AS GOTO, FOLKS. IF THEY WERE, > THEY'D ALL BE CALLED "GOTO". More fuel. I just (Wednesday) went through & put gotos into the main program of all my little utilities. Why? Because "break" doesn't work the same way as continue when you're inside a switch. I got sick and tired of messing with flags & had the choice of either using gotos or turning my switches (another structured goto, by the way) into if-then-else chains. It would be nice if 'C' didn't overload keywords this way. Perhaps 'exit' could have been used here. BTW: I wholeheartedly agree with Michael's comments. -- -- Peter da Silva -- UUCP: ...!shell!{baylor,graffiti}!peter; MCI: PDASILVA; CIS: 70216,1076
ron@brl-smoke.ARPA (Ron Natalie <ron>) (01/20/86)
> > BREAK, RETURN AND CONTINUE ARE NOT THE SAME AS GOTO, FOLKS. IF THEY WERE, > > THEY'D ALL BE CALLED "GOTO". > > More fuel. I just (Wednesday) went through & put gotos into the main > program of all my little utilities. Why? Because "break" doesn't work > the same way as continue when you're inside a switch. Wonderful, break never works the same way as continue, or else they'd be called the same thing. It is possible to do terribly unstructured things with GOTO, BREAK, RETURN, and CONTINUE, but one should not attempt to enforce structured programming at the language level because it won't work. You can write structured code in any language, and real programmers can code Fortran programs in any language as well. -Ron