Live data from Hacker News

Goto (2007)

beej.us

141–150 of 207 posts

Re: Goto (2007)

#141

Probably coming too late to the discussion, but commenting anyway... The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks }…

Can you give an example of where you've used goto in C or a C-like language? Or a rough estimate of the number of times you've found goto to be the best solution? I agree with your post in principle, but in practice I can't think of a single example where I've used a goto that wasn't eventually refactored to something better that didn't have the goto. Edit: Probably the most common example (and one given early in the…

> deeply nested loops. [...] I will prefer to hide one or more of the inner loops inside of a function call.

And I would too... but sometimes there are too many local variables involved to do that cleanly. And, ultimately, if you are making your code less clean to avoid using a goto then you avoided unwisely.

> Can you give an example of where you've used goto in C or a C-like language?

Well there have been several fairly normal examples cited by others. Let me set out my stall and present a more controversial one.

Suppose I have a switch() block (which is sorta a glorified goto already!) and I have two cases that have the same epilogue. For example:

  switch (get_next_thing_to_do()) {
  case ACTION_A:
      do_action_a();
      goto common_post_action_cleanup;

  case ACTION_B:
      do_action_b();
    common_post_action_cleanup:
      cleanup_something();
      z = nullptr;
      x = x->next;
      // yadda yadda
      break;

   case ACTION_C:
      do_action_c();
      // NOTE: we don't need to do the "cleanup" here
      break;
   }
Now the first thing you should try is to put all of the shared code in its own function. Again, this isn't always practical if what you need to do effects a lot of local variables. It also means that those "cleanup" steps are now implemented far away from the rest of the logic which might hurt comprehensibility on its own.

Another option is to just suck it up, repeat the code twice, and then just trust that the compiler will clean it up. First, that won't be as attractive if you have to do it 10 times instead of twice. Second, you're now violating the DRY principal which to me is sacrosanct. Experience has taught me that if someone updates the cleanup code for ACTION_A they will forget to update ACTION_B at the same time. If you care that the two cases always end with the same epilogue you need to have a single copy of the code.

You can try to get back into compliance with DRY by using something like a C macro, but now you're not winning the war against ugly code.

Finally you could just put the cleanup code after the switch(), either by retesting the enum value (requiring it to be stored in a temporary variable) or by adding some other new cleanup_needed boolean flag. But now we're adding control flow that wasn't there before. I personally believe that doesn't help readability or maintainability. You're now just adding more variables to understand and probably getting worse generated assembly to boot.

While many programmers will look at this and feel vaguely icky about the presence of a "goto" if you really stop and look at the block of code it's actually quite readable...at least as long as the label and the goto are near each other. It is immediately apparent that two "states" are sharing some code and why.

This little example is obviously made up. However I've spent a lot of my career writing things like hand-crafted state machines and situations exactly like this (where two "states" are different but share the same epilogue) comes up all the time. State machine code like this is famously hard to read but often sharing code via goto like this is the least-bad option for expressing non-hierarchical control flow.

Re: Goto (2007)

#142
post #133

Earlier quoted context omitted.

You have "free(buf_2); // Safe if null", but if CHECK(buf_1) turns into a "goto error", won't buf_2 be uninitialized? And so can take on any value?

You are correct, will edit. C is hard, writing C in the browser sans coffee is harder. :-)

I do think this illustrates one of the issues with goto: normally the compiler would be able to warn that you were using buf2 potentially uninitialized, but I think you wouldn't get a warning in this case.

Re: Goto (2007)

#143

Probably coming too late to the discussion, but commenting anyway... The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks }…

Can you give an example of where you've used goto in C or a C-like language? Or a rough estimate of the number of times you've found goto to be the best solution? I agree with your post in principle, but in practice I can't think of a single example where I've used a goto that wasn't eventually refactored to something better that didn't have the goto. Edit: Probably the most common example (and one given early in the…

state machines a la finite automata, which (for examples) are generated by compiler parser generators like yacc ?

Re: Goto (2007)

#144

Earlier quoted context omitted.

Often this is true, but those smaller methods might need to share so much local state that the solution is worse than the problem.

This is where languages that permit nested functions come in handy. You can locally factor out new functions while preserving access to variables in the lexical scope without needing to pass them in as parameters or lift them into a higher level scope (object, class, global, file, etc.).

That's a good solution. The next problem is that many code patterns, such as state machines, if naively converted from `goto` to `call()` will consume a lot of unnecessary stack space. This might not be a problem with a language/compiler that supports tail call optimization.

Re: Goto (2007)

#145

Probably coming too late to the discussion, but commenting anyway... The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks }…

Yeah to those still adamant about it, if you've ever used a: - function call - if sentence - any kind of loop You've used a GOTO under the hood. I hope you can live with yourself, you monster :)

> You've used a GOTO under the hood.

That it's under the hood is the point of using constrained control-flow control. It's significantly easier to reason about `if` and `while` and `for` than about `goto`. That is basically the same reason functional programming have more HoFs than `fold`. You can do everything with `fold` and that's a problem because it creates way more cognitive overhead for the reader, they can't know what the code is trying to achieve until they get all the details, nor is the compiler able to check anything.

Likewise goto.

Re: Goto (2007)

#146
post #58

I think we must distinguish between forward and backwards goto. And whether or not you are entering blocks. If you are not entering blocks, forward goto is usually fine, and for me, there is no good reason to think is is worse than break, continue and return. In fact, I would more readily ban mid-function return than this kind of goto. Backwards goto is when you get spaghetti code, there is some use for it, but it is…

This is generally my sentiment as well. The reason to avoid using goto is if it makes following the flow of execution for us humans difficult, which typically happens when jumping backwards.

Using goto judiciously to more easily escape a deeply nested block of code and/or jump to a cleanup section is not hard to follow.

Re: Goto (2007)

#147

Earlier quoted context omitted.

This is where languages that permit nested functions come in handy. You can locally factor out new functions while preserving access to variables in the lexical scope without needing to pass them in as parameters or lift them into a higher level scope (object, class, global, file, etc.).

That's a good solution. The next problem is that many code patterns, such as state machines, if naively converted from `goto` to `call()` will consume a lot of unnecessary stack space. This might not be a problem with a language/compiler that supports tail call optimization.

Right. Absent TCE and inline functions (the latter is now pretty much bog standard in every language in popular use, at least every compiled language), goto for state machines and similar uses can be much more efficient and also handle the concern of blowing up your stack. If you have TCE and inline functions, then mutual recursion is a perfectly efficient way to handle that kind of situation that is often (but not always) clearer than goto. And if you pair that with nested functions so that you can close over some common lexical scope, you eliminate the need to use global variables or to thread data through each function call (keeping your parameters to a minimum).

Re: Goto (2007)

#148

Probably coming too late to the discussion, but commenting anyway... The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. Other than simple integer FOR loops, basically all control-flow in the FORTRAN of those days was accomplished with GOTO -- and to numbered lines, not labels. Things we take for granted in all languages today like { code blocks }…

I agree with you completely. I once made a poster (I wish I'd kept it) where I was tracing out the behaviour of a single mega function with around 50 labels in it, and gotos all over the place. I worked on a reimplementation, slowly picking apart the function into subfunctions, while loops, recursive function calls, etc. Took me about 2 weeks.

That must have been so satisfying once it was done.

Re: Goto (2007)

#149
post #14
post #9

Blanket rules like "never use `goto`" are generally not great. A big part of the job of a software developer is knowing when to use what bits of a language. Sometimes code with `goto` is simply easier to understand. You have to be careful, but dismissing it completely is throwing out a tool.

People who give blanket rules such as never to use `goto` clearly do not understand where this idea comes from or why it should not be used. Many years ago in my undergrad I submitted a programming assignment in C which used `goto` for cleanup, as is customarily done in the Linux Kernel and other C software and I had points taken off for using `goto`. Heh.

> People who give blanket rules such as never to use `goto` clearly do not understand where this idea comes from or why it should not be used.

The idea comes from Dijkstra and he sure as hell did consider it a blanket rule: "I became convinced that the go to statement should be abolished from all 'higher level' programming languages (i.e. everything except, perhaps, machine code)."

Re: Goto (2007)

#150
post #67

In the past 20 years I've used a goto a few times, and then refactored it out once I was able to look at the problem with a clear head. I remember that, every time, I replaced the goto by breaking up a large method into smaller methods, and then replacing the goto with either return statements or logic that would essentially return. Yes, a goto is part of our programmers' toolbox. But, now even I consider a "good got…

Often this is true, but those smaller methods might need to share so much local state that the solution is worse than the problem.

I tend to find that to be itself a code smell. But it happens. When it does, I like using local functions that just close over the same state.
Post reply on HN