【编程思考】如何使用叛逆的goto ?


分类: C++   81人阅读  评论(0)  收藏  举报
goto C++ EleGameStudio 编程 语言

目录(?)[+]

转载请注明本文地址:

http://blog.csdn.net/elezeor/article/details/11707283

In this thread, we look at examples of good uses of goto in C or C++. It's inspired by an answer which people voted up because they thought I was joking.

Summary (label changed from original to make intent even clearer):

infinite_loop: // code goes here goto infinite_loop;

Why it's better than the alternatives:

  • It's specific. goto is the language construct which causes an unconditional branch. Alternatives depend on using structures supporting conditional branches, with a degenerate always-true condition.
  • The label documents the intent without extra comments.
  • The reader doesn't have to scan the intervening code for early breaks (although it's still possible for an unprincipled hacker to simulate continue with an early goto).

Rules:

  • Pretend that the gotophobes didn't win. It's understood that the above can't be used in real code because it goes against established idiom.
  • Assume that we have all heard of 'Goto considered harmful' and know that goto can be used to write spaghetti code.
  • If you disagree with an example, criticize it on technical merit alone ('Because people don't like goto' is not a technical reason).

Let's see if we can talk about this like grown ups.

Edit

This question seems finished now. It generated some high quality answers. Thanks to everyone, especially those who took my little loop example seriously. Most skeptics were concerned by the lack of block scope. As @quinmars pointed out in a comment, you can always put braces around the loop body. I note in passing that for(;;) and while(true) don't give you the braces for free either (and omitting them can cause vexing bugs). Anyway, I won't waste any more of your brain power on this trifle - I can live with the harmless and idiomatic for(;;) and while(true) (just as well if I want to keep my job).

Considering the other responses, I see that many people view goto as something you always have to rewrite in another way. Of course you can avoid a goto by introducing a loop, an extra flag, a stack of nested ifs, or whatever, but why not consider whether goto is perhaps the best tool for the job? Put another way, how much ugliness are people prepared to endure to avoid using a built-in language feature for its intended purpose? My take is that even adding a flag is too high a price to pay. I like my variables to represent things in the problem or solution domains. 'Solely to avoid a goto' doesn't cut it.

I'll accept the first answer which gave the C pattern for branching to a cleanup block. IMO, this makes the strongest case for a goto of all the posted answers, certainly if you measure it by the contortions a hater has to go through to avoid it.

share | improve this question
 
8  
I don't get why the gotophobes don't just mandate "#define goto report_to_your_supervisor_for_re_education_through_labour" at the top of the project's include file. If it's always wrong, make it impossible. Otherwise, it's sometimes right... –  Steve Jessop  Oct 30 '08 at 1:36
6  
"can't be used in real code"? I use it in "real" code any time it's the best tool for the job. "Established idiom" is a nice euphemism for "blind dogmatism". –  Dan Moulding  Aug 18 '10 at 15:58
7  
I agree that "goto" can be useful (there are great examples below), but I disgagree with your specific example. A line that says "goto infinite_loop" sounds like it means "go to the part of the code where we're going to start looping forever," as in "initialize(); set_things_up(); goto infinite_loop;" when what you really mean to convey is "begin the next iteration of the loop we're already in," which is completely different. If you're trying to loop, and your language has constructs designed specifically for looping, use those for clarity. while(true) {foo()} is pretty unambiguous. –  Josh  Dec 21 '10 at 19:00
3  
One major downside to your example is that it is not apparent which ohter places in the code may decide to jump to this label. A "normal" infinite loop (for (;;)) has no surprising entry points. –  jalf  Jan 5 '11 at 19:56
3  
@György: yes, but that is not an entry point. –  jalf  Mar 5 '11 at 10:36
show 3 more comments

16 Answers

active oldest votes
up vote 55 down vote accepted

Heres one trick I've heard of people using. I've never seen it in the wild though. And it only applies to C because C++ has RAII to do this more idiomatically.

void foo() { if (!doA()) goto exit; if (!doB()) goto cleanupA; if (!doC()) goto cleanupB; // everything succeed return; cleanupB: undoB(); cleanupA: undoA(); exit: return; }
share | improve this answer
 
5  
knew I shoulda taken that "speed typing" course in high school! Good answer. –  Adam Liss  Oct 29 '08 at 4:10
6  
The extra blocks cause unnecessary indentation that's hard to read. It also doesn't work if one of the conditions is inside a loop. –  Adam Rosenfield  Oct 29 '08 at 4:42
16  
You can see a lot of this kind of code in most low level Unix things (like the linux kernel, for example). In C, that's the best idiom for error recovering IMHO. –  David Cournapeau  Oct 29 '08 at 7:25
4  
As cournape mentioned, the Linux kernel uses this style all the time. –  CesarB  Oct 29 '08 at 11:00
5  
Not only linux kernel take a look in Windows driver samples from microsoft and you will find same pattern. In general this is a C way to handle exceptions and very useful one :). I usually prefer only 1 label and in few cases 2. 3 can be avoided in 99% of cases. –  Ilya  Oct 29 '08 at 12:37
show 5 more comments
up vote 54 down vote

The classic need for GOTO in C is as follows

for ... for ... if(breakout_condition) goto final; final:

There is no straightforward way to break out of nested loops without a goto.

share | improve this answer
 
33  
Most often when this need comes up I can use a return instead. But you need to be writing small functions for it to work out that way. –  Darius Bacon  Oct 29 '08 at 5:50
6  
I definitely agree with Darius - refactor it to a function and return instead! –  metao  Oct 29 '08 at 6:13
7  
@metao: Bjarne Stroustrup disagree. On his C++ Programming Language book, this is exactly the example given of a "good use" of goto. –  paercebal  Oct 29 '08 at 9:45
14  
@Adam Rosenfield: The whole problem with goto, highlighted by Dijkstra, is that structured programming offers more comprehensible constructs. Making code harder to read in order to avoid goto proves that the author failed to understand that essay... –  Steve Jessop  Oct 30 '08 at 0:43
4  
@Steve Jessop: Not only have people misunderstood the essay, most of the "no go to" cultists don't understand the historical context in which it was written. –  JUST MY correct OPINION  Apr 23 '11 at 9:09
show 5 more comments
up vote 23 down vote

Here's my non-silly example, (from Stevens APITUE) for Unix system calls which may be interrupted by a signal.

restart: if (system_call() == -1) { if (errno == EINTR) goto restart; // handle real errors }

The alternative is a degenerate loop. This version reads like English "if the system call was interrupted by a signal, restart it".

share | improve this answer
 
3  
this way is used for example linux scheduler have a goto like this but i would say there are very few cases where backwards goto's are acceptable and in general should be avoided. –  Ilya  Oct 29 '08 at 12:38
4  
@Amarghosh, continue is just a goto in a mask. –  jball  Jun 3 '10 at 16:31
2  
@jball ...hmm.. for the sake of argument, yeah; you can make good readable code with goto and spaghetti code with continue.. Ultimately it depends on the person who writes the code. The point is it is easy to get lost with goto than with continue. And newbies normally use the first hammer they get for every problems. –  Amarghosh  Jun 4 '10 at 4:06
2  
@Amarghosh. Your 'improved' code isn't even equivalent to the original - it loops forever in the success case.–  fizzer  Jun 4 '10 at 12:05
3  
@fizzer oopsie.. it's gonna need two else breaks (one for each ifs) to make it equivalent.. what can I say... goto or not, your code is only as good as you :( –  Amarghosh  Jun 4 '10 at 12:15
show 3 more comments
up vote 12 down vote

If Duff's device doesn't need a goto, then neither should you! ;)

void dsend(int count) { int n; if (!count) return; n = (count + 7) / 8; switch (count % 8) { case 0: do { puts("case 0"); case 7: puts("case 7"); case 6: puts("case 6"); case 5: puts("case 5"); case 4: puts("case 4"); case 3: puts("case 3"); case 2: puts("case 2"); case 1: puts("case 1"); } while (--n > 0); } }

code above from Wikipedia entry.

share | improve this answer
 
2  
This is an example of a logical fallacy also known as the "appeal to authority". Check it out on Wikipedia. –  Marcus Griep  Oct 29 '08 at 4:51
7  
humor is often unappreciated here... –  Steven A. Lowe  Oct 29 '08 at 5:10
2  
I wouldn't hire a humourless programmer. –  Mitch Wheat  Oct 29 '08 at 5:19
7  
does duff's device make beer? –  Steven A. Lowe  Oct 29 '08 at 22:07
5  
this one can make 8 at a time ;-) –  Jasper Bekkers  Oct 31 '08 at 18:54
show 5 more comments
up vote 12 down vote

Knuth has written a paper "Structured programming with GOTO statements", you can get it e.g. fromhere. You'll find many examples there.

share | improve this answer
 
This paper looks deep. I will read it though. Thanks –  fizzer  Oct 29 '08 at 22:09
2  
That paper is so out of date, it's not even funny. Knuth's assumptions there simply don't hold any longer. –  Konrad Rudolph  Jun 29 '09 at 15:17
3  
Which assumptions? His examples are as real today for a procedural language as C (he gives them in some pseudo-code) as they were at that time. –  zvrba  Jun 29 '09 at 17:08
2  
im dissappointed you didnt write:: You can GOTO 'here' to get it, the pun to me would have been to unbearable not to make! –  RhysW  Jan 8 at 13:58
up vote 9 down vote

I have nothing against gotos in general, but I can think of several reasons why you wouldn't want to use them for a loop like you mentioned:

  • It does not limit scope hence any temp variables you use inside won't be freed until later.
  • It does not limit scope hence it could lead to bugs.
  • It does not limit scope hence you cannot re-use the same variable names later in future code in the same scope.
  • It does not limit scope hence you have the chance of skipping over a variable declaration.
  • People are not accustomed to it and it will make your code harder to read.
  • Nested loops of this type can lead to spaghetti code, normals loops will not lead to spaghetti code.
share | improve this answer
 
is inf_loop: {/* loop body */} goto inf_loop; better? :) –  quinmars  Oct 29 '08 at 9:39
1  
for the first 4 points yes –  Brian R. Bondy  Oct 29 '08 at 13:09
1  
Points 1-4 are dubious for this example even without the braces. 1 It's an infinite loop. 2 Too vague to address. 3 Trying to hide a variable of the same name in an enclosing scope is poor practice. 4. A backward goto can't skip a declaration. –  fizzer  Oct 30 '08 at 7:44
 
1-4 as with all infinite loops, you usually have some kind of break condition in the middle. So they are applicable. Re a bakward goto can't skip a declaration... not all gotos are backwards... –  Brian R. Bondy  Oct 30 '08 at 23:36
up vote 8 down vote

Very common.

do_stuff(thingy) { lock(thingy); foo; if (foo failed) { status = -EFOO; goto OUT; } bar; if (bar failed) { status = -EBAR; goto OUT; } do_stuff_to(thingy); OUT: unlock(thingy); return status; }

The only case I ever use goto is for jumping forwards, usually out of blocks, and never into blocks. This avoids abuse of do{}while(0) and other constructs which increase nesting, while still maintaining readable, structured code.

share | improve this answer
 
4  
I think this is the typcial C way of error handling. I can not see it replaced nicely, better readable any other way Regards –  Friedrich  Oct 29 '08 at 6:25
 
Why not... do_stuff(thingy) { RAIILockerObject(thingy); ..... /* -->*/ } /* <---*/ :) –  Петър Петров  Apr 22 at 1:04
 
@ПетърПетров That's a pattern in C++ that has no analogue in C. –  ephemient  Apr 22 at 17:38
up vote 7 down vote

@fizzer.myopenid.com: your posted code snippet is equivalent to the following:

 while (system_call() == -1) { if (errno != EINTR) { // handle real errors break; } }

I definitely prefer this form.

share | improve this answer
 
1  
OK, I know the break statement is just a more acceptable form of goto.. –  Mitch Wheat  Oct 29 '08 at 4:16
8  
To my eyes, it's confusing. It's a loop which you don't expect to enter in the normal execution path, so the logic seems backwards. Matter of opinion, though. –  fizzer  Oct 29 '08 at 4:23
1  
Backwards? It starts, it continues, it falls through. I think this form more obviously states its intentions. –  Mitch Wheat  Oct 29 '08 at 4:25
7  
I'd agree with fizzer here that the goto provides a clearer expectation as to the condition's value. –  Marcus Griep  Oct 29 '08 at 4:48
3  
In which way is this snippet easier to read than fizzer's. –  erikkallen  Dec 26 '08 at 18:59
show 4 more comments
up vote 6 down vote

Here is an example of a good goto:

// No Code
share | improve this answer
 
3  
"NO Goto for you!" –  Mitch Wheat  Oct 29 '08 at 4:06
 
er, no gotos? (unsuccessful attempt at funny :d ) –  moogs  Oct 29 '08 at 4:18
 
LOL!! –  Anonymous Type  Dec 22 '10 at 1:00
up vote 6 down vote

One good place to use a goto is in a procedure that can abort at several points, each of which requires various levels of cleanup. Gotophobes can always replace the gotos with structured code and a series of tests, but I think this is more straightforward because it eliminates excessive indentation:

if (!openDataFile())
  goto quit;

if (!getDataFromFile())
  goto closeFileAndQuit;

if (!allocateSomeResources)
  goto freeResourcesAndQuit;

// Do more work here....

freeResourcesAndQuit:
   // free resources
closeFileAndQuit:
   // close file
quit:
   // quit!
share | improve this answer
 
I would replace this with a set of functions: freeResourcesCloseFileQuitcloseFileQuit, and quit.–  RCIX  Dec 6 '09 at 8:43
3  
If you created these 3 extra functions, you'd need to pass pointers/handles to the resources and files to be freed/closed. You'd probably make freeResourcesCloseFileQuit() call closeFileQuit(), which in turn would call quit(). Now you have 4 tightly coupled functions to maintain, and 3 of them will probably be called at most once: from the single function above. If you insist on avoiding goto, IMO, nested if() blocks have less overhead and are easier to read and maintain. What do you gain with 3 extra functions? –  Adam Liss  Dec 6 '09 at 23:59
up vote 5 down vote

Even though I've grown to hate this pattern over time, it's in-grained into COM programming.

#define IfFailGo(x) {hr = (x); if (FAILED(hr)) goto Error} ... HRESULT SomeMethod(IFoo* pFoo) { HRESULT hr = S_OK; IfFailGo( pFoo->PerformAction() ); IfFailGo( pFoo->SomeOtherAction() ); Error: return hr; }
share | improve this answer
 
up vote 1 down vote

I've seen goto used correctly but the situations are normaly ugly. It is only when the use of goto itself is so much less worse than the original. @Johnathon Holland the poblem is you're version is less clear. people seem to be scared of local variables:

void foo() { bool doAsuccess = doA(); bool doBsuccess = doAsuccess && doB(); bool doCsuccess = doBsuccess && doC(); if (!doCsuccess) { if (doBsuccess) undoB(); if (doAsuccess) undoA(); } }

And I prefer loops like this but some people prefer while(true).

for (;;) { //code goes here }
share | improve this answer
 
1  
Never, ever, ever compare a boolean value to true or false. It's already a boolean; just use "doBsuccess = doAsuccess && doB();" and "if (!doCsuccess){}" instead. It's simpler and protects you from clever programmers who write things like "#define true 0" because, well, because they can. It also protects you from programmers who mix int's and bool's, and then assume any non-zero value is true. –  Adam Liss  Dec 23 '10 at 2:04
 
Thank you and point taken == true removed. This is closer to my real style anyway. :) –  Charles Beattie Jan 5 '11 at 19:30
up vote 1 down vote

If your intent is to run a (small) service like:

int main(int argc, char *argv[]) { setup_blabla(argc, argv); for (;;) // I prefer this to while(1) since some // compilers (MSVC) warn about the latter { ... } }

then an exotic alternative that I like is to use tail call optimization like that:

int main(int argc, char *argv[]) { setup_blabla(argc, argv); // this sets up globals return service_loop(); } volatile int interrupted; int service_loop() { // Do something // You can even use static variables here. ... ... if (interrupted) { cleanup(); return EXIT_FAILURE; } ... ... return service_loop(); // Restart the loop }

The intent is quite clean this way. No need for while(true) and break statements. Even a background service will have signal handlers which will trigger cleanup code and early exit, so you'll have to break or goto anyway.

The real (only ?) interest is that you save a scope: no nested loop with an extra pair of braces insideservice_loop.

Externalizing the loop implies that you must dispatch (eg. via a switch statement) error codes outside theservice_loop function in order to restart the loop. No such thing here.

Beware however that coding standard nazis will object to using TCO instead of the politically correctfor(;;) or while(1). Send them to hell.

share | improve this answer
 
This code is NOT guarranteed to UNWIND the Stack! Some compilers will do a Stack Overflow! –  Петър Петров Apr 22 at 1:07
up vote 0 down vote

My gripe about this is that you lose block scoping; any local variables declared between the gotos remains in force if the loop is ever broken out of. (Maybe you're assuming the loop runs forever; I don't think that's what the original question writer was asking, though.)

The problem of scoping is more of an issue with C++, as some objects may be depending on their dtor being called at appropriate times.

For me, the best reason to use goto is during a multi-step initialization process where the it's vital that all inits are backed out of if one fails, a la:

if(!foo_init()) goto bye; if(!bar_init()) goto foo_bye; if(!xyzzy_init()) goto bar_bye; return TRUE; bar_bye: bar_terminate(); foo_bye: foo_terminate(); bye: return FALSE;
share | improve this answer
 
up vote 0 down vote

I don't use goto's myself, however I did work with a person once that would use them in specific cases. If I remember correctly, his rationale was around performance issues - he also had specific rules for how. Always in the same function, and the label was always BELOW the goto statement.

share | improve this answer
 
2  
Additional data: note that you cannot use goto to go outside the function, and that in C++, it is forbidden to bypass an object construction through a goto –  paercebal  Oct 29 '08 at 9:58
up vote -1 down vote

@Greg:

Why not do your example like this:

void foo() { if (doA()) { if (doB()) { if (!doC()) { UndoA(); UndoB(); } } else { UndoA(); } } return; }
share | improve this answer

你可能感兴趣的:(C++)