News for hackers. Stuff that matters.
Re:Goto (2007) by bodyfour on Thursday October 21, 2021 @11:45AM (#28946179)
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 } didn't exist.

Indeed the first thing you do when you try to understand code of that era is to print it out, get your markers out, and start drawing arrows all over the green-bar paper so you can start to make some sense of where control flow is going.

In that world, yes, GOTO was a big problem and its use was rightly replaced with the structured control flow that we all use today. But that world has also been nearly gone for 30 years now. It's completely unrelated to using a C "goto" statement with a well-chosen label name in order to accomplish some otherwise-awkward control flow.

On projects I've worked on I tend to develop a reputation for heavy goto use and I do so unapologetically. Sometimes it honestly is the cleanest way to implement something and I don't think it should be feared one bit.

Just because it was a bad idea to do everything with a GOTO 60 years ago shouldn't mean don't do some things with goto today.

Re:Goto (2007) by GrumpyYoungMan on Thursday October 21, 2021 @06:01PM (#28951358)
> "The issue I think is that people today read "GOTO Considered Harmful" without really understanding the world at the time. ... Just because it was a bad idea to do everything with a GOTO 60 years ago shouldn't mean don't do some things with goto today."

Every time this comes up, I always encourage people to read an excellent analysis by David Tribble "Go To Statement Considered Harmful: A Retrospective" that goes over Dijkstra's essay line by line and explains what it means in a more modern context: http://david.tribble.com/text/goto.html It really should be required reading in CS programs.

I've used and still use goto in the few niches it makes sense to, primarily error handling and multi-level break in C/C++.

Re:Goto (2007) by User23 on Saturday October 23, 2021 @02:36PM (#28971076)
> "Dijkstra later abandoned the search for program provability and turned instead to the study of techniques for correct program derivation"

It's odd how even today very few people in the "formal verification" world understand this distinction. Naturally verifying a derived program is trivial, since it's literally a proof by construction. I've attempted many times to explain this distinction, and they inevitably just start babbling about the limitations of the tooling they use. A sad case of man with a hammer syndrome and probably an illustration of my own failure to achieve clarity I guess.

> "However, to some extent Dijkstra's principle has not been fully realized when we observe the complexity that must be dealt with by real-world programming tasks, such as multitasking, multithreading, interrupt handling"

Dijkstra designed the first proven correct interrupt handler for a multitasking OS two years before the Goto Letter[1][2] so I think we can rest assured that he was aware of these issues.

[1] https://en.wikipedia.org/wiki/THE_multiprogramming_system

[2] https://www.cs.utexas.edu/users/EWD/transcriptions/EWD01xx/E...

Re:Goto (2007) by wglb on Thursday October 21, 2021 @11:14PM (#28953353)
This is a good balanced article.

What is often missing from these goto discussions is the dialog between Knuth and Dijkstra on this topic.

There is another discussion between the two of them about a problem requiring no less than four stacks to understand.

Re:Goto (2007) by 0xffff2 on Thursday October 21, 2021 @12:26PM (#28946881)
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 article) is deeply nested loops. This is a good example of where I'm very unlikely to use goto. Almost certainly, I will prefer to hide one or more of the inner loops inside of a function call.

Re:Goto (2007) by jjav on Thursday October 21, 2021 @03:09PM (#28949468)
Think of everythin which in java you would put in the catch and finally blocks. Those are nearly always most cleanly handled in C code by doing a goto to the cleanup/return section at the end of the function.

You could work around this via tons of deeply nested blocks, but that makes the code very unreadable. The goto cleanup pattern makes the code intuitive and very readable.

I still have a printout somewhere of an application from the 80s, maybe a dozen pages in small font, every few lines it jumps via goto to somewhere else, depending on state (global variables, of course). It was a serious nightmare to figure out where the code is going. That was the historical context of "goto considered harmful". It sure was! Code like that doesn't exist today, the lessons have been learned. It had nothing to do with clean jump to a cleanup section (which is what a finally{} block is, after all).

Re:Goto (2007) by davidwf on Thursday October 21, 2021 @01:04PM (#28947551)
It's a very common pattern for C memory allocation checking. A public example I know off the top of my head can be seen here: https://github.com/zedshaw/learn-c-the-hard-way-lectures/blo... (the implementation of the CHECK macro). That's in a C tutorial but I've implemented a version of that macro frequently.

Let's say you need to dynamically allocate two buffers in a function and want to make sure they are freed at the end of your call. You can use this macro like so:

  int two_bufs(int n, int m) {
    int *buf_1 = NULL;
    int *buf_2 = NULL;
    buf_1 = calloc(n, sizeof(int));
    CHECK(buf_1);
    buf_2 = calloc(m, sizeof(int));
    CHECK(buf_2);
 
    // ... lots of cool things with buf_1 and buf_2

  error:
    free(buf_1);
    free(buf_2); // Safe if null

  }
Re:Goto (2007) by jefftk on Thursday October 21, 2021 @01:13PM (#28947680)
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?
Re:Goto (2007) by davidwf on Thursday October 21, 2021 @01:17PM (#28947731)
You are correct, will edit. C is hard, writing C in the browser sans coffee is harder. :-)
Re:Goto (2007) by jefftk on Thursday October 21, 2021 @01:55PM (#28948369)
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) by Jtsummers on Thursday October 21, 2021 @03:33PM (#28949770)
One of the examples in the linked article showed the compiler emitting a warning when a variable wasn't initialized because the goto skipped past that line, it's in 31.7. I don't know what compilers will or will not give you that warning, but at least the one used for the article does. So it ought to catch the problem with the initial version of the example above as well.
Re:Goto (2007) by lilyball on Thursday October 21, 2021 @03:25PM (#28949680)
The Clang Static Analyzer could probably find this, if the compiler itself doesn't notice.
Re:Goto (2007) by InfiniteRand on Thursday October 21, 2021 @12:36PM (#28947068)
It's good for a simplified version of exception handling for C (granted C does have setjmp/longjmp but that's more like the complicated version of exception handling)

If you hit an error condition in deeply nested logic and need to go to common error handling code or go to a recovery point, then goto makes sense.

Often times in these scenarios simplifying the logic is also an option, but that isn't always the case, especially when dealing with external interfaces that have a lot of potential error conditions that cannot be dealt with easily from the immediate caller (I'm thinking primarily of system interfaces, but also potentially library interfaces).

From what I've seen this is the only really good scenario for goto and I think the examples in the article more or less fall into this category of escaping from deeply nested error cases.

Re:Goto (2007) by brundolf on Thursday October 21, 2021 @12:41PM (#28947138)
Makes me realize that the way some people use caught-exceptions is actually just reaching for goto in a language that doesn't have it
Re:Goto (2007) by anfelor on Thursday October 21, 2021 @01:02PM (#28947526)
Another example not covered on the article is tail call optimisation. GCC is not very good at it and manually adding it using goto can sometimes speed code up by ~20%. Very useful when writing down a complex recursive algorithm that you can't easily express as a loop.
Re:Goto (2007) by NtGuy25 on Thursday October 21, 2021 @01:04PM (#28947562)
If you have a dispatcher or an infinite state machine, say an emulator. Or even just anything in general with cleanup, you use goto to "goto ERRORCLEANUP". So you have one exit condition to clean up. This is recommended in CERT c.
Re:Goto (2007) by throwaway09223 on Thursday October 21, 2021 @05:00PM (#28950787)
There's a good real world example of goto for progressive cleanup here: https://github.com/square/pam_krb5_ccache/blob/master/pam_kr...
Re:Goto (2007) by throwawaylinux on Thursday October 21, 2021 @09:22PM (#28952783)
The Linux kernel has 28 million lines of C code, and 180 thousand goto statements.

1 every 160 lines is a goto, and that's just a naive count so includes all header and preprocessor gunk, whitespace, some generated C code, comments, etc.

If you're talking about more pure lines if C, it would probably be close to 1% of statements are goto.

It might not be everyone's cup of tea as the pinnacle of C programming, but it's a real highly used medium-large project that has coped well with a pretty high rate of change from a very wide spectrum of contributors. So it is a good real world example of open C code you can look at that is maintainable while making a lot of use of goto.

Re:Goto (2007) by GrumpyYoungMan on Thursday October 21, 2021 @09:23PM (#28952789)
There are a few decent examples given by the Linus kernel devs on a LKML thread where an unfortunate soul wandered in and suggested that they refactor to get rid of GOTOs: https://lkml.org/lkml/2003/1/12/126

> "I will prefer to hide one or more of the inner loops inside of a function call."

That risks the additional unnecessary overhead of a function call, which can be expensive in a loop, and not all compilers are as good at optimizing as they should be.

Re:Goto (2007) by mumblemumble on Thursday October 21, 2021 @12:28PM (#28946910)
What's wrong with the examples in the article?
Re:Goto (2007) by bodyfour on Thursday October 21, 2021 @01:50PM (#28948273)
> 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) by gsinclair on Thursday October 21, 2021 @09:13PM (#28952729)
I really like this answer, but I do think a flag “cleanup_required” could be a clean way to express the code as well, with “if cleanup_required …” after the switch block.

But yep, I have no complaints with the code as presented.

Re:Goto (2007) by ghoward on Friday October 22, 2021 @02:15AM (#28954183)
For a rough estimation, you can use my bc at [1] which uses goto extensively for safe and effective use of setjmp() and longjmp(), while having no memory leaks (to my knowledge).

When going through the code, though, be aware that some goto's are in #define's to make it easier to work with setjmp().

If you're lazy, it's about 10k loc with about 100-200 uses of goto.

[1]: https://git.yzena.com/gavin/bc

Re:Goto (2007) by idiocrat on Thursday October 21, 2021 @08:10PM (#28952394)
The times I found I used the goto statements are retrials.

For example, if a file is locked for writing, I want to wait for 5, then 10, then 15 seconds, retrying each time, before giving up.

In such case I do not want the retrials loop to be my main control structure.

Instead, I jump from inside the IO exception catch block to the ReTry label, restarting the whole operation.

This makes the flow fairly clean.

Re:Goto (2007) by codr7 on Thursday October 21, 2021 @12:33PM (#28946991)
Re:Goto (2007) by slaymaker1907 on Thursday October 21, 2021 @12:48PM (#28947259)
Replying to infiniterand, a table of function pointers is going to generally have way more overhead than an indirect goto. An indirect goto is essentially a single assembly jump instruction. This is also often faster than a while loop plus switch since it is friendlier to the branch predictor.
Re:Goto (2007) by InfiniteRand on Thursday October 21, 2021 @12:39PM (#28947113)
I'm sure there's probably a reason for doing things this way, but it seems like that code would make more sense as a function pointer table and a series of functions. Was that not preferred for aesthetic reasons or for some specific technical reason?
Re:Goto (2007) by codr7 on Thursday October 21, 2021 @01:19PM (#28947772)
Speed.

Your suggestion adds indirect memory access plus function call, for every instruction.

I've tried every other way I can think of, and nothing runs faster from my experience.

Re:Goto (2007) by adrian_b on Thursday October 21, 2021 @03:00PM (#28949354)
Few programmers use goto, because they have been indoctrinated against it and they have no understanding about when goto is bad and when goto is good, but there are many cases where using goto is better (i.e. more clear and/or faster) than what most programmers write now in such occasions.

In some of those cases there are alternatives that could be better than goto, but they cannot be used because they need features that are missing from the programming languages popular now.

One example is state machines. Many programmers when having to implement a state machine use a state variable, e.g. an enumeration and a big "switch" or "case" structure, depending on the language, with a branch for each state.

This kind of implementation follows the method taught now to avoid goto's by adding unnecessary variables that are used to store some state when some condition is detected with the purpose to make a jump later with an extra if or switch instead of doing a jump with a goto immediately when the condition has been detected.

This method of eliminating goto's is pretty stupid, because it not only adds useless variables but it also doubles the number of conditional branches, because each

if (condition) goto label;

is replaced by:

if (condition) variable = value;

...

if (variable == value) {...} or switch (variable) {...}

Besides the accesses to non-cached memory, the conditional branches are the main causes of low performance, so doubling the number of conditional branches can result in much worse performance.

When the second conditional branch is a "switch", that is even worse, because it is typically implemented as an indexed jump, which will be frequently mispredicted.

Instead of using a state variable and a "switch" with branches for states, the right implementation is to have sequences of statements for the states, with the first statement in each sequence labeled with the name of the state.

During each state, when the condition for the transition to another state is detected, a goto is executed, with the name of the next state.

Anyone who believes that this is less clear or more difficult to read than the variant with state variables is delusional, because in the variant with goto's you see immediately the effect of a transition condition, while in the variant with state variables you just see some value being stored and you do not know its purpose until possibly much later when you discover that it is tested for a second conditional branch. Even after you see this use, you are not certain that this is the only place where the variable is used, it might also have other uses and you might need extra time until you are certain about what the program really does.

A more elegant solution for state machines is possible only in languages where tail call optimization is guaranteed.

In such languages each state may be implemented as a separate function and the goto's may be replaced with function invocations in final positions, which are transformed by the compiler into jumps.

While not so many programmers need to write state machines, there are much more frequent uses where goto is superior, e.g. for error management in cases where exceptions are inappropriate, but describing when, why and how would be long.

In general all the problems for which goto was considered harmful are not solved by eliminating goto but by restricting its behavior.

There was a series of research articles by various authors, published between 1970 and 1975, whose conclusion was that the right kind of goto should be allowed to make only forward jumps and that the labels must have a scope restricted to the block in which they are declared. Therefore a goto should be able only to exit from a block but not to enter a block.

An example of a programming language that had this restricted form of goto was the language Mesa, from Xerox.

In C however, it would not be possible to restrict goto only to forward jumps without simultaneously adding guaranteed tail call optimization.

Re:Goto (2007) by jjtheblunt on Thursday October 21, 2021 @02:12PM (#28948652)
state machines a la finite automata, which (for examples) are generated by compiler parser generators like yacc ?
Re:Goto (2007) by CJefferson on Thursday October 21, 2021 @11:59AM (#28946421)
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.

Re:Goto (2007) by kazinator on Thursday October 21, 2021 @07:01PM (#28951874)
I could refactor a self-contained 50 label goto graph into a network of tail recursing functions pretty quickly; as little as an hour, in the absence of nasty confounding factors.

If the goto graph implements a calculation with no side effects like I/O, my network will be all pure functions, too.

Completely beyond the reproach of any CS academic. :)

There is a mechanical way to do this.

1. We turn all of the function's local variables into function arguments. Each of our tail recursive functions will take these arguments.

2. Every labeled node becomes a function.

3. Every goto becomes a function call invoking the function that its target label was turned into, passing in each of its own argument to the corresponding parameter of that function. The function's return value is immediately returned.

4. Every variable assignment before a goto is turned into an argument expression in the function call which passes the new value to the corresponding argument. E.g. instead

     a++; d *= 2;
     goto foo;
we have

     return foo(a + 1, b, c, d * 2, e, ...);
Once you have this working, then you refactor. For instance, if you have a function like this:

     int foo(int a, int b, int c, ..., int z)
     {
        return a + c;
     }
which does not pass its arguments to another tail-called function, then you can eliminate all of the arguments and just turn it into:

     int foo(int a, int c)
     {
        return a + c;
     }
and of course edit all of the calls to foo accordingly, and simplify those places also. In this way, you will "discover" simpler functions that work with just a subset of the state transfer.
Re:Goto (2007) by prox on Thursday October 21, 2021 @04:36PM (#28950513)
You maybe the right person to ask… Aren’t function / functioncallers a form of goto?
Re:Goto (2007) by CJefferson on Thursday October 21, 2021 @06:13PM (#28951470)
Technically yes, all code eventually compiles to goto, or goto equivalents (simplifying slightly)

However, the idea behind "goto considered harmful" is almost all code can be written with function, loops and if/else statements and get just as efficient, and much, much easier to understand.

Re:Goto (2007) by kazinator on Thursday October 21, 2021 @07:04PM (#28951890)
All goto code can be mechanically translated into a network of mutually tail-calling functions arranged into an identically shaped call graph that is hardly easier to understand.

What you can do with goto-based code is refactor it into a form that obeys certain conventions such that it is easy to mechanically translate to such a tail-call newtork, but then stop short of actually doing it.

Then when reading the code, just pretend you're looking at function calls.

Re:Goto (2007) by munificent on Thursday October 21, 2021 @02:39PM (#28949105)
That must have been so satisfying once it was done.
Re:Goto (2007) by blntechie on Thursday October 21, 2021 @12:12PM (#28946637)
My first job was writing VB.NET code and it was hammered to us not to use GOTO. Once for a critical production bug, using GOTO was the quickest solution to fix and I used it with a comment to not fire me. And that code lived on for years.
Re:Goto (2007) by ehutch79 on Thursday October 21, 2021 @03:32PM (#28949764)
Sometimes a little bit of poison can make a great medicine.

Goes both ways though.

Re:Goto (2007) by moffkalast on Thursday October 21, 2021 @12:00PM (#28946433)
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 :)

Re:Goto (2007) by brundolf on Thursday October 21, 2021 @12:09PM (#28946596)
But there's a reason we come up with sanctioned, constrained concepts built atop more powerful lower-level ones. Constraints in code benefit maintainability, correctness, and sometimes even performance.

"Yeah to those still adamant about it, if you've ever used statically-typed code, you've used untyped assembly under the hood. I hope you can live with yourself, you monster :)"

Re:Goto (2007) by agent327 on Thursday October 21, 2021 @12:24PM (#28946844)
You put a smiley so I suppose it's possible you know better, but this kind of shallow, thoughtless critique is just painful to read, especially when following such a well-presented, historically accurate description of the debate surrounding goto. This is especially true given that Dijkstra himself refutes your argument, such as it is, in the opening paragraph of his seminal paper.
Re:Goto (2007) by masklinn on Thursday October 21, 2021 @02:20PM (#28948778)
> 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) by GrumpyYoungMan on Thursday October 21, 2021 @12:58PM (#28947448)
What's even worse is that, at the machine language level, condition and unconditional jumps are all that there are; we've all been using GOTOs constantly without even knowing it. :)
Re:Goto (2007) by andi999 on Thursday October 21, 2021 @01:45PM (#28948207)
The real hidden gotos are:

- state machines - early return of a function

Re:Goto (2007) by 1-more on Thursday October 21, 2021 @01:11PM (#28947644)
a case statement is the one where the GOTO peeks out the most, imo.
Re:Goto (2007) by CodeMage on Thursday October 21, 2021 @07:15PM (#28951979)
This is my pet peeve with how they teach kids programming. Granted, I only have anecdata on that topic, gleaned from how they teach my own kid at school, but I still can't shake the feeling that we're doing them a disservice by making them skip straight to JavaScript or Python.

I wonder what it would be like if kids started with BASIC. And not Visual Basic or any other modern, structured flavor. No, I mean the ancient stuff:

    10 PRINT "All your base are belong to us!"
    20 GOTO 10
Limited in what it can do, simple to learn, with just enough fun stuff in it (like PLOT, DRAW, INKEY$, INPUT, etc.)

Then go on to assembly language, again on some very limited and simple machine, along the lines of the good old ZX Spectrum 48 or Commodore 64.

Then move on to something like C or Pascal. Or even Python and JavaScript if you want. But the idea is that going through BASIC and assembly, they'll learn two important things: 1) how computers actually work (even though it's super-simplified, it should still be useful), and 2) why you need structured programming.

Or maybe I'm just being naive. I really would like to see if that works out, though.

Re:Goto (2007) by gsinclair on Thursday October 21, 2021 @08:06PM (#28952371)
I teach high school CS and have eventually come to the same insight, and found that novice programmers do indeed understand primitive BASIC much more easily than Python. Python seems simple to us, but there is so much abstraction even in its base-level syntax. I am gradually redeveloping my course materials to use BASIC (via replit.com) in the early stages.
Re:Goto (2007) by ouid on Friday October 22, 2021 @12:35AM (#28953785)
I thought the problem with unstructured goto is that it is extremely hard to reason formally about code that uses it.
Re:Goto (2007) by 1vuio0pswjnm7 on Thursday October 21, 2021 @11:11PM (#28953330)
SNOBOL, the non-numerical computing counterpart to FORTRAN, also uses GOTOs, exclusively.

I still run spitbol. Regardless of its assembly-like control flow, I think the pattern matching beats PCRE, and Lua's implementation of LPeg.

Re:Goto (2007) by westcort on Thursday October 21, 2021 @04:22PM (#28950352)
What I want is a language that consists ONLY of GOTO statements. The Turing tape concept indicates that it should be possible.
Re:Goto (2007) by schoen on Thursday October 21, 2021 @04:26PM (#28950410)
Subtract and branch if negative is more or less that:

https://en.wikipedia.org/wiki/One-instruction_set_computer#S...

Re:Goto (2007) by rstuart4133 on Thursday October 21, 2021 @07:52PM (#28952272)
> Indeed the first thing you do when you try to understand code of that era is to print it out, get your markers out, and start drawing arrows all over the green-bar paper so you can start to make some sense of where control flow is going.

The first thing I do is change the indentation to reflect the structure. The basic blocks then become obvious. If you don't do that (and most people in the era didn't), it's about as hard to read as left aligned C code. With it, it's not that much harder to read than indented C code.

Unfortunately not indenting the code just reflected a bigger problem: programmers back then were totally oblivious to the structure indenting revealed or how they could be used to reduce the difficulty in reading the code: basic blocks, limited scopes and the like. It's not that they were lazy, it's that they didn't understand how code is built.

Dijkstra taught computer science. That is what he was up against. At the time, all programming languages used goto almost exclusively for flow control. In some ways, it is the simplest way to do it. One construct did the job of the multitudes we use today: for, while, until and even recursion. However if you didn't follow some simple rules the goto's invariably ended up as spaghetti and the end result ended up being there was far more spaghetti in production than structured code.

The two simple rules are: 1. backward goto's were only for loops, and 2. you must not goto inside an inner basic block. These are exact same rules for and while loops with {} blocks enforce. But it takes some self discipline to apply those rules, and no one is going to do it if they don't understand why they are important. Such understanding requires good high level intuition about how code is structured. It took a long while to teach such intuition. If you got rid of the goto's by replacing them with higher level constructs like for and while, you not only got rid of the problem - you also forced young students to think in terms of code flow. That is what Dijkstra was arguing for in "Goto's considered harmful".

Today, times have changed. You very rarely see a badly structured goto in the Linux kernel code, even though C allows it. I'm not sure why that is - because I'm pretty sure if Dijkstra hadn't won out and Javascript and it's ilk still had goto's then shudder. The coding standards in web programming is a cluster fuck as it is: I swear there isn't a week go by when I don't come up against a broken web page. Just this week I was forced to fire up the browsers js debugger to change a field on a linkedin.com form, and manually do "$0.value = 'yes'" to move on. ffs.

But yes, in projects with coding standards as high as the Linux kernels, everybody that (is allowed to?) contribute seems to have a very good understanding of how to make code that others find easy to read and understand, a ban on gotos would be counterproductive. Despite the discipline they require, gotos can be used to make the code cleaner and shorter. You see numerous examples of that in the kernel.