Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

As someone who occasionally dabbles in C code, I am interested in knowing what is the modern take on `goto`s.

I know they are "harmful" but I often come across code riddled with goto statements [1] and I personally feel that as long as it makes the code readable without significantly obfuscating the logic, goto is a perfectly fine way of doing things (although popular opinion and consideration for best practices have more or less forced me to remove goto from my list of C tools). Also, the fact that it maps almost directly to asm makes it easier to reason about the generated machine code (although if that's a significant reason for using goto is questionable).

[1] https://github.com/zedshaw/mongrel2/blob/master/src/http11/h...

Zed Shaw's Mongrel2 server code linked to in another thread.



Goto is fine. The 'harmful' style was using them in favor of ifs and loops.

The things I've seen people do to avoid a goto are pretty awful though. If you ever use a 'do {} while (0)' just to break out of it, you should feel bad. Goto is much clearer and cleaner than nonsense like that.


Agree with above comment. Linux kernel code, especially device driver code use goto a lot.


I think one uses goto's for two sometimes three reasons.

1. Sometimes one feels the need to abuse exceptions. But C doesn't have exceptions. So one abuses the old goto instead.

2. Sometimes the code is far more readable if you use a goto to short circuit a complex block of code. Which will become insufferably more complicated if it has to say keep track of a trivial case. if(this and not trivial case) else this and not trivial. if(case a and not trivial case) else {if not trivial case)

3. You can use long jumps to do exception handling stuff. I've never had to actually do this.

One comment. I remember trying to read ancient code that abused goto's mostly because the programmer was desperately trying to fit everything into 4k of prom in languages that didn't support structured code. That was the kind of stuff Dijkstra was bitching about, not uses 1, 2, and 3. And actually since C has always had modern control structures goto just is not abused much in practice. Probably the opposite.

Side note.

int my_var = 3; my_var = "abc";

Just usually generates a warning when compiled. If run my_var will usually get loaded with the address of "abc". If you follow it with the statement

printf("my_var=%s\n", my_var); // this will throw a warning

It'll print 'my_var=abc'


Some languages, eg. BASIC, used goto's instead of functions. It is easy to see how this can lead to a horrible mess of spaghetti code.


That's not really the best example of using goto's, as that code is generated from this file:

https://github.com/zedshaw/mongrel2/blob/master/src/http11/h...

A nicer example of the use of goto's in Mongrel2 is the check macro:

https://github.com/zedshaw/mongrel2/blob/a884aef0f4a460e130d...

The convention of the project is that every function that has to deal with an error condition has an "error" label. These macro a used to jump to that label to clean up the function before returning. Here's an example:

https://github.com/zedshaw/mongrel2/blob/a884aef0f4a460e130d...

I think that this use of gotos is more clear than the alternative of a lot of nested if statements checking for success or having the clean up logic duplicated in a lot of places.


In the C code I've written, I've found there's really one major, common use for goto: stack unwinding. It usually looks something like

    status_t foo (result_t (*result))
    {
        status_t status = good;

        resource1_t r1;
        if (failed (status = get_r1 (&r1)))
            goto end;

        resource2_t r2;
        if (failed (status = get_r2 (&r2)))
            goto cleanup_r1;

        resource3_t r3;
        if (failed (status = get_r3 (&r3)))
            goto cleanup_r2;

        status = get_result (result, r1, r2, r3);

    cleanup_r3:
        release_r3 (r3);
    cleanup_r2:
        release_r2 (r2);
    cleanup_r1:
        release_r1 (r1);
    end:
        return status;
    }
Personally, though, I prefer using C++ and destructors (and, where appropriate, exceptions):

    result_t foo ()
    {
        // Allow exceptions to propagate
        auto r1 = get_r1 ();
        auto r2 = get_r2 ();
        auto r3 = get_r3 ();

        return get_result (r1, r2, r3);
    }
Exceptions aren't always appropriate, but the C++ still usually ends up a little cleaner. I would love to see something like Haskell's Maybe monad that lets you write code like the above, but returning status information instead of throwing exceptions.


If it's genuinely easier to understand with gotos, you can safely ignore the opinion of those that object.

Gotos are a specialised tool that many misuse, especially historically, but when you need them, you need them.


Without having RAII from C++/Rust, it's really tricky to correctly release resources when something exceptional happens.


Never ever ever use goto's. If you have to use a goto, rewrite the code.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: