0

I have read that GOTO is bad, but how do I avoid it? I don't know how to program without GOTO. In BASIC I used GOTO for everything. What should I use instead in C and C++?

I used GOTO in BASIC like this:

MainLoop:
INPUT string$   
IF string$ = "game" THEN   
GOTO game
ENDIF
phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 2
    Loops, if-then-else, etc. Just read any good book on C or C++; it probably won't *mention* `goto` in the early chapters. – Fred Foo Aug 03 '13 at 16:37
  • 3
    function calls, loops, switch statements, polymorphism. I never encountered a situation where I needed `goto` in C++. – juanchopanza Aug 03 '13 at 16:38
  • learn about basic c++ control flow, I've never programmed in BASIC but I would imagine it should have had those constructs, not just GOTO – aaronman Aug 03 '13 at 16:38
  • 1
    You can use `goto` instead of `GOTO` in C/C++. ;-) – James M Aug 03 '13 at 16:38
  • An example how "avoid goto" here http://stackoverflow.com/questions/11921071/should-i-avoid-goto-in-situations-like-this?rq=1 – alexbuisson Aug 03 '13 at 16:40
  • 6
    Please get a [good book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) and learn some of the language first. As you learn about things such as loops and functions you will start to realize that `goto` is not necessary. – Captain Obvlious Aug 03 '13 at 16:49
  • 1
    Also note that in C++, `goto` could seriously mess things up. So it is not only less likely that you will need it in C++, it is more likely that it will break your program. – juanchopanza Aug 03 '13 at 17:02
  • This is a troll, surely? – Martin James Aug 03 '13 at 17:46
  • @MartinJames no its not, i guess that the op is really young so he doesn't know... – Quonux Aug 03 '13 at 17:50
  • @juanchopanza: Could you show me a C++ example where `goto` messes things up? – user541686 Aug 04 '13 at 05:10
  • If you think `goto` is something you should avoid, take a look at [some ReactOS source code](http://svn.reactos.org/svn/reactos/trunk/reactos/ntoskrnl/ps/process.c?view=markup). It has some of the cleanest C code I've ever seen, and it uses `goto` all the time. – user541686 Aug 04 '13 at 05:15

9 Answers9

13

Consider the following piece of C++ code:

void broken()
{
    int i = rand() % 10;
    if (i == 0) // 1 in 10 chance.
        goto iHaveABadFeelingAboutThis;

    std::string cake = "a lie";

    // ...
    // lots of code that prepares the cake
    // ...

iHaveABadFeelingAboutThis:
    // 1 time out of ten, the cake really is a lie.
    eat(cake);

    // maybe this is where "iHaveABadFeelingAboutThis" was supposed to be?
    std::cout << "Thank you for calling" << std::endl;
}

Ultimately, "goto" is not much different than C++'s other flow-control keywords: "break", "continue", "throw", etc; functionally it introduces some scope-related issues as demonstrated above.

Relying on goto will teach you bad habits that produce difficult to read, difficult to debug and difficult to maintain code, and it will generally tend to lead to bugs. Why? Because goto is free-form in the worst possible way, and it lets you bypass structural controls built into the language, such as scope rules, etc.

Few of the alternatives are particularly intuitive, and some of them are arguably as ambiguous as "goto", but at least you are operating within the structure of the language - referring back to the above sample, it's much harder to do what we did in the above example with anything but goto (of course, you can still shoot yourself in the foot with for/while/throw when working with pointers).

Your options for avoiding it and using the language's natural flow control constructs to keep code humanly readable and maintainable:

  • Break your code up into subroutines.

Don't be afraid of small, discrete, well-named functions, as long as you are not perpetually hauling a massive list of arguments around (if you are, then you probably want to look at encapsulating with a class).

Many novices use "goto" because they write ridiculously long functions and then find that they want to get from line 2 of a 3000 line function to line 2998. In the above code, the bug created by goto is much harder to create if you split the function into two payloads, the logic and the functional.

void haveCake() {
    std::string cake = "a lie";

    // ...
    // lots of code that prepares the cake
    // ...

    eat(cake);
}

void foo() {
    int i = rand() % 10;
    if (i != 0) // 9 times out of 10
        haveCake();
    std::cout << "Thanks for calling" << std::endl;
}   

Some folks refer to this as "hoisting" (I hoisted everything that needed to be scoped with 'cake' into the haveCake function).

  • One-shot for loops.

These are not always obvious to programmers starting out, it says it's a for/while/do loop but it's actually only intended to run once.

for ( ; ; ) { // 1-shot for loop.
    int i = rand() % 10;
    if (i == 0) // 1 time in 10
         break;
    std::string cake = "a lie";
    // << all the cakey goodness.

    // And here's the weakness of this approach.
    // If you don't "break" you may create an infinite loop.
    break;
}

std::cout << "Thanks for calling" << std::endl;
  • Exceptions.

These can be very powerful, but they can also require a lot of boiler plate. Plus you can throw exceptions to be caught further back up the call stack or not at all (and exit the program).

struct OutOfLuck {};

try {
    int i = rand() % 10;
    if (i == 0)
        throw OutOfLuck();
    std::string cake = "a lie";
    // << did you know: cake contains no fat, sugar, salt, calories or chemicals?
    if (cake.size() < MIN_CAKE)
        throw CakeError("WTF is this? I asked for cake, not muffin");
}
catch (OutOfLuck&) {} // we don't catch CakeError, that's Someone Else's Problem(TM).

std::cout << "Thanks for calling" << std::endl;

Formally, you should try and derive your exceptions from std::exception, but I'm sometimes kind of partial to throwing const char* strings, enums and occasionally struct Rock.

try {
    if (creamyGoodness.index() < 11)
        throw "Well, heck, we ran out of cream.";
} catch (const char* wkoft /*what kind of fail today*/) {
    std::cout << "CAKE FAIL: " << wkoft << std::endl;
    throw std::runtime_error(wkoft);
}

The biggest problem here is that exceptions are intended for handling errors as in the second of the two examples immediately above.

kfsone
  • 23,617
  • 2
  • 42
  • 74
  • 1
    "did you know: cake contains no fat, sugar, salt, calories or chemicals?" +1 for the laugh, but no. :) –  Aug 04 '13 at 21:58
  • 1
    @hvd caveat emptor, `std::string cake = "a lie";` :) – kfsone Aug 04 '13 at 22:08
  • @kfsone - Plus one for *Most entertaining* – ryyker Sep 09 '13 at 23:15
  • "1-shot for loop" hmm, you seem to have misspelt `do { /* ... */ } while (false)` ;-) – underscore_d Jul 02 '16 at 18:59
  • @kfsone What benefit does `for (;;) { /* ... */ break; }` have? Just seems like more typing to me, and like it might be too easy to accidentally delete the `break`. Still, the more the merrier. – underscore_d Jul 02 '16 at 19:57
  • @underscore_d at some point I annotated that as a weakness of the approach, and at the time I was writing I had in mind that `do { ... } while ()` has caused more of the bugs I've found in other people's code than the for loops or even gotos. Flow control shouldn't be this difficult :) – kfsone Feb 22 '23 at 17:52
6

There are several reasons to use goto, the main would be: conditional execution, loops and "exit" routine.

Conditional execution is managed by if/else generally, and it should be enough

Loops are managed by for, while and do while; and are furthermore reinforced by continue and break

The most difficult would be the "exit" routine, in C++ however it is replaced by deterministic execution of destructors. So to make you call a routine on exiting a function, you simply create an object that will perform the action you need in its destructor: immediate advantages are that you cannot forget to execute the action when adding one return and that it'll work even in the presence of exceptions.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
3

Usually loops like for, while and do while and functions have more or less disposed the need of using GOTO. Learn about using those and after a few examples you won't think about goto anymore. :)

DaMachk
  • 643
  • 4
  • 10
3

Edsger Dijkstra published a famous letter titled Go To Statement Considered Harmful. You should read about it, he advocated for structured programming. That wikipedia article describes what you need to know about structured programming. You can write structured programs with goto, but that is not a popular view these days, for that perspective read Donald Knuth's Structured Programming with goto Statements.

amdn
  • 11,314
  • 33
  • 45
1

goto is not inherently bad, it has it's uses, just like any other language feature. You can completely avoid using goto, by using exceptions, try/catch, and loops as well as appropriate if/else constructs.

However, if you realize that you get extremly out of your way, just to avoid it, it might be an indiaction that it would be better to use it.

Personally I use goto to implement functions with single entry and exit points, which makes the code much more readable. This is the only thing where I still find goto usefull and actually improves the structure and readabillity of the code.

As an example:

int foo()
{
    int fd1 = -1;
    int fd2 = -1;
    int fd3 = -1;

    fd1 = open();
    if(fd1 == -1)
        goto Quit:

    fd2 = open();
    if(fd2 == -1)
        goto Quit:

    fd3 = open();
    if(fd3 == -1)
        goto Quit:

     ... do your stuff here ...

Quit:
   if(fd1 != -1)
      closefile();

   if(fd2 != -1)
      closefile();

   if(fd3 != -1)
      closefile();
}

In C++ you find, that the need for such structures might be drastically reduced, if you properly implement classes which encapsulate access to resources. For example using smartpointer are such an example.

In the above sample, you would implement/use a file class in C++, so that, when it gets destructed, the file handle is also closed. Using classes, also has the advantage that it will work when exceptions are thrown, because then the compiler ensures that all objects are properly destructed. So in C++ you should definitely use classes with destructors, to achieve this.

When you want to code in C, you should consider that extra blocks also add additional complexity to the code, which in turn makes the code harder to understand and to control. I would prefer a well placed goto anytime over a series of artifical if/else clauses just to avoid it. And if you later have to revisit the code, you can still understand it, without following all the extra blocks.

Devolus
  • 21,661
  • 13
  • 66
  • 113
  • 2
    You might also mention, that this use of `goto` is actually part of the linux styleguide :-) – cmaster - reinstate monica Aug 03 '13 at 16:46
  • Yes, this is an example where `goto` helps readability, but that isn't what the question asks. The question asks when *not* to use `goto` (where the OP would use `GOTO` in the equivalent BASIC code because of limitations of the language) –  Aug 03 '13 at 16:53
  • @hvd, that's why I added this just an example, because i addressed the other points as well. – Devolus Aug 03 '13 at 16:54
  • 5
    In C++, this is a bad example, as it's better done using the RAII pattern. Then, you can just `return` or throw an exception in case something goes wrong, and cleanup happens automatically. But I agree that in C this pattern can occasionally be useful. – Thomas Aug 03 '13 at 16:58
  • 3
    You briefly mentioned some other points, but if I were the OP, and I were reading this answer, I would read it as "it's okay to just continue using `goto`". In that light, your answer is correct, but probably unhelpful. However, I cannot judge if this is how others interpret your answer as well, and if others don't, please do disregard my comment. –  Aug 03 '13 at 16:59
  • no need to use `goto`s here, and in fact it will make performance worse because even if only fd3 gets error, it must also evaluate the 2 `if`s above to close fd1, fd2. Close file and return directly in each of the first file opening `if`s makes code much more readable – phuclv Aug 04 '13 at 02:41
  • @LưuVĩnhPhúc, performance penaltiy is a minor issue here. Checking directly with return, means that in each subsequent check, you have to remember to release all previous resources. if you have more resources, this easily leads to forgetting some stuff, and especially if the code is changed later, you tend to forget this. I have seen this error a lot and this is the main reason why a goto is the better choice here. This is a trivial obvious example, but in real code, this happens a lot. – Devolus Aug 04 '13 at 06:32
1

goto is now displaced by other programming constructs like for, while, do-while etc, which are easier to read. But goto still has it's uses. I use it in a situation where different code blocks in a function (for e.g., which involve different conditional checks) have a single exit point. Apart from this one use for every other thing you should use appropriate programming constructs.

Sarat
  • 113
  • 1
  • 1
  • 7
1

Maybe instead of

if(something happens) 
   goto err;

err:
   print_log()

one can use :

do {

    if (something happens)
    {
       seterrbool = true;       
       break;  // You can avoid using using go to I believe
    } 

} while (false) //loop will work only one anyways
if (seterrbool)
   printlog();

It may not seem friendly because in the example above there is only one goto but will be more readable if there are many "goto" .

Kadir Erdem Demir
  • 3,531
  • 3
  • 28
  • 39
0

BASIC originally is an interpreted language. It doesn't have structures so it relies on GOTOs to jump the specific line, like how you jump in assembly. In this way the program flow is hard to follow, making debugging more complicated.

Pascal, C and all modern high-level programming languages including Visual Basic (which was based on BASIC) are strongly structured with "commands" grouped into blocks. For example VB has Do... Loop, While... End While, For...Next. Even some old derivatives of BASIC support structures like Microsoft QuickBASIC:

DECLARE SUB PrintSomeStars (StarCount!)
REM QuickBASIC example
INPUT "What is your name: ", UserName$
PRINT "Hello "; UserName$
DO
   INPUT "How many stars do you want: ", NumStars
   CALL PrintSomeStars(NumStars)
   DO
      INPUT "Do you want more stars? ", Answer$
   LOOP UNTIL Answer$ <> ""
   Answer$ = LEFT$(Answer$, 1)
LOOP WHILE UCASE$(Answer$) = "Y"
PRINT "Goodbye "; UserName$
END

SUB PrintSomeStars (StarCount)
   REM This procedure uses a local variable called Stars$
   Stars$ = STRING$(StarCount, "*")
   PRINT Stars$
END SUB

Another example in Visual Basic .NET

Public Module StarsProgram
   Private Function Ask(prompt As String) As String
      Console.Write(prompt)
      Return Console.ReadLine()
   End Function

   Public Sub Main()
      Dim userName = Ask("What is your name: ")
      Console.WriteLine("Hello {0}", userName)

      Dim answer As String

      Do
         Dim numStars = CInt(Ask("How many stars do you want: "))
         Dim stars As New String("*"c, numStars)
         Console.WriteLine(stars)

         Do
            answer = Ask("Do you want more stars? ")
         Loop Until answer <> ""
      Loop While answer.StartsWith("Y", StringComparison.OrdinalIgnoreCase)

      Console.WriteLine("Goodbye {0}", userName)
   End Sub
End Module

Similar things will be used in C++, like if, then, for, do, while... which together define the program flow. You don't need to use goto to jump to the next statement. In specific cases you can still use goto if it makes the control flow clearer, but in general there's no need for it

phuclv
  • 37,963
  • 15
  • 156
  • 475
0

This implementation of the function above avoids using goto's. Note, this does NOT contain a loop. The compiler will optimize this. I prefer this implementation.

Using 'break' and 'continue', goto statements can (almost?) always be avoided.

int foo()
{
    int fd1 = -1;
    int fd2 = -1;
    int fd3 = -1;

    do
    {
        fd1 = open();
        if(fd1 == -1)
            break;

        fd2 = open();
        if(fd2 == -1)
            break:

        fd3 = open();
        if(fd3 == -1)
            break;

         ... do your stuff here ...
    }
    while (false);

   if(fd1 != -1)
      closefile();

   if(fd2 != -1)
      closefile();

   if(fd3 != -1)
      closefile();
}
Bill H
  • 1