6 ms·
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
by Aardwolf 13y 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?
- antirez 13y agoGoto 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.
- coherentpony 13y agoI 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.
- dllthomas 13y agoIf 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;
- scott_s 13y agoBecause 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#L427 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.
- garenp 13y agoIn 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'.
- dllthomas 13y agoYou could leave nested loops with a condition flag, which could produce something semantically equivalent to the goto. It seems like the logic here might be simple enough that the actual check could be eliminated in the generated code by a Sufficiently Smart Compiler (that could actually exist), if there was sufficient demand for it. I'm not sure it's actually more readable, though.
- garenp 13y agoYeah, of course I'm aware, it's just that there is a time/space trade-off, so I think folks who argue that goto should never be used or has no legitimate uses, are just taking too much of a hard-line. And I think it just highlights a lack of understanding about what might make goto "bad" in programming--IMHO it's "bad" when it makes control flow more complex, which undermines maintainability. Common "cleanup" idioms that only jump forward are not that bad, because they don't make control flow particularly more difficult to follow. (Whereas jumping backwards, especially across many state changes, can be very hard to follow.)
- dllthomas 13y ago"it's just that there is a time/space trade-off," I don't see a time-space tradeoff here. Compiled naively, I expect the version with flags to be slower and take more space. Compiled sufficiently smartly, I expect them to be equivalent. I expect real compilers to be close enough to the latter for most but not all purposes, but I think if the flag version was more readable and maintainable the place to focus (collectively, medium term) would be on making compilers smart enough. Obviously, in the short term on specific projects you do what you need to. "I think folks who argue that goto should never be used or has no legitimate uses, are just taking too much of a hard-line." I agree, but that's because there are places where use of Go To makes things more readable and maintainable, not - primarily - because they are actually needed, per se, even with performance constraints (in the overwhelming majority of cases). 'And I think it just highlights a lack of understanding about what might make goto "bad" in programming--IMHO it's "bad" when it makes control flow more complex, which undermines maintainability.' I agree wholeheartedly.
- cmollis 13y agoagreed. 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)
- dllthomas 13y agoGotos are bad when used for unconstrained flow control. Restricted use of gotos to implement exception handling is, as you say, not bad.
- agumonkey 13y agoalso suggested here http://c.learncodethehardway.org/book/ex20.html http://c.learncodethehardway.org/book/ex20.html
- jhallenworld 13y agoWell 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).
- nitrogen 13y agoLook at the man page for alloca(). Stack-allocated strings are more dangerous in the face of overflow than heap-allocated strings.
- jhallenworld 13y agoWell 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).
- coherentpony 13y agoYou still have to 'free' in destructors in C++. Destructors don't free your memory for your custom types for you.
- vinkelhake 13y agoThe point is that the 'free' is now in one place and not in every place that uses the class.
- T-zex 13y agoYou mean heap allocated types? Stack allocated will be deallocated implicitly.
- dllthomas 13y agoYou need to free heap-allocated objects, but stack allocated objects are important and useful (particularly with RAII) and cleanly reversing such allocations eliminates a class of bugs.
- jlarocco 13y agoStack allocated objects are cleaned up automatically in C, too...
- dllthomas 13y agoRAII. [1] Stack allocated objects have their memory freed in C. Other resources are not released. Consider the following C, with and without gcc extensions: #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <fcntl.h> #include <unistd.h> #ifndef GCC_EXTENSIONS #define GCC_EXTENSIONS 1 #endif int main() { { #if GCC_EXTENSIONS void close_fd(int *fdp) { if(*fdp >= 0) close(*fdp); } #else #define __attribute__(...) #endif int fd __attribute__((cleanup(close_fd))) = -1; fd = open("somefile", O_CREAT | O_WRONLY, 0644); system("ls /proc/self/fd"); } system("ls /proc/self/fd"); } with gcc extensions: 0 1 2 3 4 0 1 2 3 without gcc extensions: 0 1 2 3 4 0 1 2 3 4 [1] http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initial...
- dllthomas 13y agoIf 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.
- vsbuffalo 13y agoYea, 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.
- scott_s 13y agoThe goto cleanup pattern is a good one. It's important to know why certain constructs are frowned upon. That pattern is not one of them. Look at the Linux kernel; it's used extensively. For example: http://lxr.free-electrons.com/source/kernel/fork.c?v=3.3#L427 http://lxr.free-electrons.com/source/kernel/fork.c?v=3.3#L42...
- lucian1900 13y ago"goto cleanup" is fine, it's precisely how exceptions work anyway. It's a commonly used pattern, most famously in the Linux kernel.
- dllthomas 13y agoFWIW, gcc does have destructors for stack-allocated data as an extension: http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html#index-g_t_0040code_007bcleanup_007d-attribute-3015 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
- effdee 13y ago"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.
- dmunoz 13y agoMany 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.
- mikevm 13y agoI 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".
- ufo 13y ago> wherever you used goto you could've used functions Ah, if only C would guarantee tail call optimization >:)
- dmunoz 13y agoI 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.
- munificent 13y ago> 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.
- ufo 13y agoI 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_To_Considered_Harmful.html 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.
- Narishma 13y ago> 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.
- Someone 13y agoRelevant 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.
- AndyKelley 13y agoThis 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/bc7d72589eb297342a7f8c0505e441e9b512daac/groove/playlist.c#L814 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.