The use of goto in C (not C++)

Started by
44 comments, last by Dmytry 15 years, 9 months ago
Quote:Original post by Zahlman
Stuff


That's indeed very elegant.

Thank you for an excellent answer, as always.
Advertisement
The enmity towards goto has got to be more tradition than anything else at this point. In the dawn of time goto-dominated spaghetti code was a real problem, and the invention and adoption of structured programming languages was a great step forward.
Now, thirty years later, people still spend large amounts of time discussing whether goto may be the best solution to some specific problem in some specific language, but it doesn't change the fact that either way no one is advocating using them in more than a handful of idiomatic cases poorly handled by the existing control structures so their impact is essentially zero. Not even the basic converts seem to be trying to abuse goto anymore.
I mean honestly on the list of hard or just plain fucked up things in C/C++ goto is just not on the radar..

Oh, by the way there is one classic uncontroversial case where goto is useful: code generators.
Quote:Original post by AcidZombie24
I want to make clear that when i used alloca, i meant alloca and NOT malloc. Malloc you free, alloca you dont. Malloc you can allocate tons of memory, alloca you cant (remember, its on the STACK).

Zahlman:
>Wait, what?

The 'other' time i use gotos, are when i need to break out of 2 loops. The break keyword unfortunately only breaks out of the current loop. I would like a break 2; keyword. (i never needed 3 but why not have it)


I think you've misunderstood. You said you never used them, and then gave an example where you do. I thought that was strange.

I've had the "break N" idea before but I suspect it leads to maintenance nightmares. Then again, maybe that's just the thing to discourage overly deep nesting. ;) You might want to look at how Java handles it: you can do things like

label: for (stuff) {    for (other stuff) {        if (weird thing) { break label; }    }}


Notice that the label applies to the loop being broken out of, not to where the code flow ends up. Also note that not only is there no 'goto' in Java, they took precautions in the design to avoid accidentally adding it in the future. ;)
Quote:Original post by implicit
The enmity towards goto has got to be more tradition than anything else at this point. In the dawn of time goto-dominated spaghetti code was a real problem, and the invention and adoption of structured programming languages was a great step forward.


I disagree. Goto is, generally speaking, a way of being maliciously lazy: not the good kind of lazy that leads to automating things you don't want to do yourself, but the bad kind of lazy that leads to not communicating your ideas to people who need to hear them.

The reason there are loop constructs in the language is so you can be expressive. Strictly speaking they are totally unnecessary. It is trivial to implement them all in terms of goto and if. We don't because (a) the person reading the code then has to figure out what's going on, and (b) the for/while/etc. form is often easier or shorter to write. In cases where (b) fails, many programmers forget about (a). (And sometimes they are simply mistaken about (b); if you get sufficiently accustomed to certain "goto idioms", you can end up missing very simple alternate structures. I've seen it happen).

Using goto to control flow reflects thinking of the program as a flowchart - with all the symbols in a line, at that. This is a poor model for how we normally approach problems.

Quote:Now, thirty years later, people still spend large amounts of time discussing whether goto may be the best solution to some specific problem in some specific language, but it doesn't change the fact that either way no one is advocating using them in more than a handful of idiomatic cases poorly handled by the existing control structures so their impact is essentially zero. Not even the basic converts seem to be trying to abuse goto anymore.


There are some pretty egregious (ab)uses in the Linux kernel code. The *actually* "idiomatic" cases are in fact not so "poorly handled by existing control structures", either. This is an excuse made by inexperienced programmers, who either haven't thought about it very hard, or got scared off by a nasty "general-case solution", or both.

I *will* agree with Linus that not having even things like break, and instead being forced to work around them with loop-exit flags, is really horrible. Break, as a concept as well as a keyword, is just fine. The SESE doctrine has IMO very much missed the point.

Quote:I mean honestly on the list of hard or just plain fucked up things in C/C++ goto is just not on the radar..


Let me tell you a story: when I learned to program, I learned BASIC first, then Turing, then C. BASIC and C have goto; Turing does not. I used C (though very infrequently, while also picking up other much friendlier languages) for years before I actually found out it has goto. My instinctive reaction was "Ewwwww, why would I want to go back to using THAT?" I swear noone had ever previously told me not to use goto at that point. :)

Quote:Oh, by the way there is one classic uncontroversial case where goto is useful: code generators.


I'm not convinced. If you're smart enough to write code that doesn't use goto, and you're smart enough to write code that writes code, why wouldn't you be smart enough to write code that writes code that doesn't use goto?
Quote:Original post by Zahlman
There are some pretty egregious (ab)uses in the Linux kernel code. The *actually* "idiomatic" cases are in fact not so "poorly handled by existing control structures", either. This is an excuse made by inexperienced programmers, who either haven't thought about it very hard, or got scared off by a nasty "general-case solution", or both.
I honestly haven't actually read more than a small bit of Linux kernel code so I couldn't say but frankly I don't think I've seen anything resembling real goto abuse in code less than a decade old (or possibly written by a newbie).
Seriously, virtually all of it has either been cleanup code as presented here, multi-level breaks, or once in a blue moon jumping into some previous if case to share code where restructuring the loops was tricky. Now I'm not saying that these are necessarily better than the alternatives, merely that they're used so rarely in practice and in such comparatively straightforward ways that it just doesn't matter either way.
I suppose I might have a different attitude if I had maintain ancient C code (and a lot of people do) but from my perspective these eternal goto-wars seem kind of silly.

Quote:I'm not convinced. If you're smart enough to write code that doesn't use goto, and you're smart enough to write code that writes code, why wouldn't you be smart enough to write code that writes code that doesn't use goto?
Well.. So far my (admittedly feeble) attempts at writing compilers have generally involved translating all control structures into generalized forms of goto, it is after all the textbook way of doing things. Now if you want to spend a bunch of extra code recreating the best-matching C structures to make the code readable or more easily optimized, then that's fine, but in practice you'd rarely bother.
Interestingly I once wrote a static recompiler which relied on tail-recursion and short functions rather than a gigantic goto mess to get extra performance. The downside here is that it wouldn't run in debug mode since the stack exploded without tail-recursion optimizations on.
Quote:Original post by implicit
Quote:I'm not convinced. If you're smart enough to write code that doesn't use goto, and you're smart enough to write code that writes code, why wouldn't you be smart enough to write code that writes code that doesn't use goto?
Well.. So far my (admittedly feeble) attempts at writing compilers have generally involved translating all control structures into generalized forms of goto, it is after all the textbook way of doing things. Now if you want to spend a bunch of extra code recreating the best-matching C structures to make the code readable or more easily optimized, then that's fine, but in practice you'd rarely bother.
Interestingly I once wrote a static recompiler which relied on tail-recursion and short functions rather than a gigantic goto mess to get extra performance. The downside here is that it wouldn't run in debug mode since the stack exploded without tail-recursion optimizations on.


Oh, you meant *that* kind of code generation. I was thinking of very large systems, genetic programming, code-as-data approaches to configuration info etc. True, the machine - and any sane bytecode - is going to implement everything with jumps anyway, and using C as a target is just a shortcut (though it's the poor man's shortcut) to portability.

Tail recursion should be convertible to plain iteration without goto fairly easily in normal cases, though?
Quote:Original post by Zahlman
Oh, you meant *that* kind of code generation. I was thinking of very large systems, genetic programming, code-as-data approaches to configuration info etc. True, the machine - and any sane bytecode - is going to implement everything with jumps anyway, and using C as a target is just a shortcut (though it's the poor man's shortcut) to portability.
It may be the poor man's portable assembly language but it's nevertheless damned useful and popular for just this purpose ;)

Quote:Tail recursion should be convertible to plain iteration without goto fairly easily in normal cases, though?
Yeah, usually. But in this case I was merely abusing tail recursion to implement goto when translating assembly language code to C, something which can feature some *truly* weird control flows and special cases.
Basically the idea was to translate each basic block of assembly code into a separate C function. Then a goto corresponds to tail recursion and an assembly function call could be translated into a real C function call rather than maintaining an explicit stack, which was much less efficient and gave C's optimizer less breathing room.

By the way this is a much broader problem with tail recursion in C as far as I'm concerned, that is you can't rely on the compiler performing it properly so it's relegated to being an optimization rather than a first-class method of flow control. My code would have a lot fewer loops and much more recursion if I didn't have to worry about running out of stack space.
Correct me if I'm wrong, but I have the impression that goto labels must be unique. That is, "error:" could only be used once rather than reused again and again in various functions. Perhaps this behavior is compiler specific and that explains why this drawback is forgotten during discussions on the propriety of using goto.
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Quote:Original post by LessBread
Correct me if I'm wrong, but I have the impression that goto labels must be unique. That is, "error:" could only be used once rather than reused again and again in various functions. Perhaps this behavior is compiler specific and that explains why this drawback is forgotten during discussions on the propriety of using goto.

Kind of. goto labels have a weird and unique scope: function-wide. In other words, you can reuse the same label in different functions, but not in the same function even if it's a different scope within that function.
void a(){  goto foo;  foo:}void b(){  goto foo;  foo: // okay!}void c(){  if(qux()){    goto foo;    foo:  }  while(bar()){    goto foo;    foo: // errorized!  }}
Quote:Original post by Gage64
Like I said, my example was illustrative. You are assuming that you can perform further operations (attempt to open a file) even if previous operations failed (the memory allocation). In your code, that's true, but what if one operation depends on a previous operation being successful? If the previous operation failed, you cant continue - you have to exit the function immediately, while making sure to cleanup any allocated resources.

Then you nest:
...int * arr = malloc(...);if(arr) {    FILE * file = fopen(...);    if(file) {        /* normal operation */        ...        fclose(file);    } else {        /* file open error reporting */    }    ...    free(arr);} else {    /* memory allocation error reporting */}


Quote:Still, it does show that elegantly avoiding a goto is not as hard as I thought in this particular case.

No, it doesn't. Avoiding goto is trivial, so much so that I've never written one in C or C++ in almost 15 years of use. And the above is not even employing function calls encapsulating atomic operations.

The only misfeature in C that tempts me to write gotos sometimes is labeled break (terminating deeply nested loops), which I invariably get around by introducing a sentry variable.

This topic is closed to new replies.

Advertisement