Comment by Aardwolf

12 years ago

The biggest missing thing in C imho is destructors, because you need to manually clean up everything whenever you leave a scope or function. That means calling "free" at every exit point.

Or alternatively, putting one "cleanup:" label at the end of the function and using "goto cleanup;" instead of break or return everywhere.

But goto is considered harmful, so that handy pattern seems unclean.

My question is: do you consider the "goto cleanup" pattern clean or not, and if not, what are better alternatives?

Many people are correctly citing the usage of goto in the Linux kernel. One thing I would like to add is what Greg Kroah-Hartman says about goto in one of his talks: only jump forward, never backwards. This is precisely what you do when you goto cleanup.

I, personally, think the pattern is clean. A labelled break is basically a goto anyway, yet not always available in your language. I never liked needing a flag to exit some nested structure early. I don't find reading that clean at all.

Also, a minor rant here: Dijkstra's "Go To Statement Considered Harmful" is often mentioned, sometime wordlessly, when goto is brought up, but it seems many of those people misunderstood what was actually being argued. Although he mentions being "convinced that the go to statement should be abolished from all 'higher level' programming languages," his main gripe was that the "unbridled use of the go to statement has an immediate consequence that it becomes terribly hard to find a meaningful set of coordinates in which to describe the process progress." He felt it was "as it stands is just too primitive; it is too much an invitation to make a mess of one's program." So, as others have pointed out already, his main issue was with goto being used in a way that resulted in unstructured, hard to understand programs.

  • I had points taken off from a C programming assignment in college once because I used goto for cleanup, and the grader blindly took off points for using goto. It took me quite a few back & forths with him to convince him that my usage was fine. His words were: "the use of goto is rather dangerous and usually leads to spaghetti code and reduction in performance (because of prediction + cache reasons), wherever you used goto you could've used functions".

    • I have heard about this happening to others. In the kernel I developed as part of an operating systems course, I pre-emptively commented my use of goto with a short justification of its usage and cited its use in the linux kernel. I didn't get any response, so I have no idea how the T.A. felt about it. I didn't lose any points, at least.

    • > wherever you used goto you could've used functions

      Ah, if only C would guarantee tail call optimization >:)

Goto for cleanup is not bad IMHO. I use it extensively...

$ cd redis; grep goto *.c | wc -l 251

All the instances are like:

    goto cleanup;
    goto error;
    goto badfmt;
    goto numargserr;

In this context is easy to read and makes the code structure better.

  • agreed. Goto's are not bad at all, particularly when dealing with error scenarios. They avoid hack-y if-then error logic (sort of like try/catch/finally)

    • Gotos are bad when used for unconstrained flow control. Restricted use of gotos to implement exception handling is, as you say, not bad.

  • I don't see the difference between this and

        cleanup(state);
        error(state);
    

    etc.

    Just pull them each out into a function. You're still DRY and don't lose flow control.

    In my honest opinion, there is never a need for goto.

    • If you are constructing arguments in order, you can destroy them in the reverse order at the exit and this lets you cleanly handle a failure in the middle of allocations:

          thing1 = allocate_thing1();
          if(!thing1) goto cleanup1;
      
          thing2 = allocate_thing2();
          if(!thing2) goto cleanup2;
      
          ...
      
      
          cleanup3:    deallocate_thing2(thing2);
          cleanup2:    deallocate_thing1(thing1);
          cleanup1:    return;

    • Because sometimes the tasks you need to perform cascade, and then you're repeating that cascade all over the place. As I pointed out upthread, check out the Linux kernel: http://lxr.free-electrons.com/source/kernel/fork.c?v=3.3#L42...

      Notice the cascade after the 'out' label; each label after it is for a different level of cleanup needed, depending on how deep into the system call the function encountered the error. Also notice that this is basically the kind of code one would generate for exceptions.

    • In performance critical nested loops, goto is the only way to exit early. A typical example here is performing a matrix multiply. In cases like this, it is absolutely necessary, because C doesn't provide a more granular 'break'.

      5 replies →

FWIW, gcc does have destructors for stack-allocated data as an extension: http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html#i...

    #include <stdio.h>
    
    typedef struct foo foo_t;
    
    void foo_cleanup(foo_t *f);
    
    struct foo { int i; };
    
    void foo_cleanup(foo_t *f) {
        printf("f %d\n", f->i);
    }
    
    int main() {
    
        foo_t a __attribute__((cleanup(foo_cleanup))) = { 7 };
    
        printf("l %d\n", __LINE__);
    
        {
            foo_t b __attribute__((cleanup(foo_cleanup))) = { 8 };
            
            printf("l %d\n", __LINE__);
        }
    
        printf("l %d\n", __LINE__);
    }


    l 18
    l 23
    f 8
    l 26
    f 7

Yea, I agree. I love C and was a C purist and flat-out refused to learn C++ well and use it in practice. This recently changed and now I quite enjoy C++. Destructors are one reason why. The whole idea of resource-managing classes and RAII is clever and really useful.

The hard part is that it takes a lot to be a good C++ programmer. C is such a smaller language, you can master a handful of best practices, gain experience, and become a competent C programmer. In contrast, C++ is such a large language you have to be active in learning everything — read Meyers, Modern C++, GoF's Design Patterns, etc. Anything short of this, and you're going to accidentally reinvent the wheel or do something stupid. But still, C++(11) is a terrific language that I'm learning to love.

> But goto is considered harmful,

Dijkstra never considered the kind of goto you describe as harmful. This is a distinction that's lost on most programmers now that the kind of languages that did have harmful gotos are dead and forgotten.

What he argued against was gotos that jump across procedure or scope boundaries.

  • I think part of the problem is that everyone heard that goto is harmful but noone ever reads the original letter, even though its reads like a short blog post (assuming they had blogs back then)

    http://www.u.arizona.edu/~rubinson/copyright_violations/Go_T...

    According to Dijkstra, the really evil gotos are the ones that force you to keep a track of the whole execution path in order to figure out the current state of the program. Its much easier when you can look at a line of code and statically know what the program state will be like at that point.

    A for loop is better than goto because you can look at a line inside it and instantly know that it will run a number of times, with the index changing in increasing order.

    gotos for resource cleanup are good because you can look at a given line of code and know what resources still need to be freed.

    Code that gets rid of gotos and break statements by blindly replacing them with tons of flags is just as bad as gotos because you still need to look at the whole execution path to figure out the state in the flags.

    • Relevant quote from "GO TO statement considered harmful": "The remark about the undesirability of the go to statement is far from new. I remember having read the explicit recommendation to restrict the use of the go to statement to alarm exits, but I have not been able to trace it; presumably, it has been made by C. A. R. Hoare."

      He doesn't explicitly say it, but it seems he agreed with that use of go to.

    • > I think part of the problem is that everyone heard that goto is harmful but noone ever reads the original letter

      Same thing for the premature optimization quote from Knuth.

  • In fact, his original title was "a case against the goto statement". It was the editor of CACM that gave it the link-baity (for the time) title "considered harmful".

"But goto is considered harmful"

GOTO _was_ considered harmful back in 1968(!) when structured programming was still in its infancy. Code of that time was usually peppered with GOTOs and therefore a pain to read.

Using GOTO for cleanup tasks is perfectly fine because it increases readability.

"goto cleanup" is fine, it's precisely how exceptions work anyway.

It's a commonly used pattern, most famously in the Linux kernel.

If you read much of the original discussion around the "Go To Statement Considered Harmful", this pattern is frequently pointed out as a reasonable use case absent proper exception handling.

Indeed, from the original editorial: "I remember having read the explicit recommendation to restrict the use of the go to statement to alarm exits, but I have not been able to trace it[.]"

It is the right way to handle exceptional conditions in modern C.

This is a question I've wondered myself. It's nice to see some different answers and reasoning here.

The solution I came up with myself is to have a destroy() function that is capable of cleaning up no matter what state the object is in.

Example: https://github.com/andrewrk/libgroove/blob/bc7d72589eb297342...

Notice all the calls to groove_playlist_destroy. And then in the destroy function it checks the conditions it has to before cleaning up.

I'm not saying it's necessarily the best answer but I think it's a pretty clean strategy.

Well here is one alternative: allocate temporary strings on a stack basis. Have some higher level functions reset the stack pointer. So now things are allocated temporarily by default, and only in the (hopefully) rare case where you need a long-lived item do you promote it to long lived. Promoting could mean copying the string to the heap, or it could just mean marking it so that the stack cleanup mechanism skips it (so perhaps you have a linked list of temporary items or something similar).

  • Look at the man page for alloca(). Stack-allocated strings are more dangerous in the face of overflow than heap-allocated strings.

    • Well I did not mean that the strings are literally on the call stack. You can make your own stack (and it doesn't have to be a stack, just have the same semantics).

You still have to 'free' in destructors in C++. Destructors don't free your memory for your custom types for you.