5 ms·
A nice alternative is to wrap malloc and give it a context argument. Then free via the context. This is really the same idea but doesn’t need any extra stuff
by sesuximo 6y ago
A nice alternative is to wrap malloc and give it a context argument. Then free via the context. This is really the same idea but doesn’t need any extra stuff in the language
Or you know... free the things you need to free and move on with your life
- coldtea 6y ago>Or you know... free the things you need to free and move on with your life And leave memory leaks, buffer overflows, and bugs in the process, and we've done for the past 40+ years...
- sesuximo 6y agoSure. But defer just changes the problem from forgetting to write free to forgetting to write defer. So we’re not really talking about provably correct solutions... just things that can work. And I think a context or just manually freeing can work nicely.
- coldtea 6y ago>Sure. But defer just changes the problem from forgetting to write free to forgetting to write defer. Yes, which is a much better formulation.
- crznp 6y agoI agree that it is better when it can be used, but I don't think it can always be used (eg: long lived memory, resources with overlapping but distinct lifetimes, legacy code). So it adds a new way to misinterpret the code: is cleanup deferred or not?
- loup-vaillant 6y ago> defer just changes the problem from forgetting to write free to forgetting to write defer. That's the case only the first time you write code: { foo *f = new_foo(); // step 1 /* lots of code */ // step 3 free(f); // step 2 } vs { foo *f = new_foo(); // step 1 defer free(f); // step 2 /* lots of code */ // step 3 } Sure, in both cases you can forget step 2. But what about review? With defer, the init and cleanup code are besides each other, and a missing defer would be immediately suspicious. Without defer, you'd have to check the end of the block to make sure the cleanup code is there. The absence of the cleanup code wouldn't jump to your eyes the same way the absence of defer would. In the long run, this makes defer significantly harder to forget. --- Another significant advantage of defer is that it can handle several exit points. Imagine this code: Foo f = new_foo(); defer free(f); if (!f) { return FAIL_FOO; } Bar b = new_bar(); defer free(b); if (!b) { return FAIL_BAR; } Baz z = new_baz(); defer free(z); if (!z) { return FAIL_BAZ; } /* business logic */ /* business logic */ /* business logic */ return SUCCESS; Now the same, without defer: Foo f = new_foo(); if (!f) { free(f); return FAIL_FOO; } Bar b = new_bar(); if (!b) { free(f); free(b); return FAIL_BAR; } Baz z = new_baz(); if (!z) { free(f); free(b); free(z); return FAIL_BAZ; } /* business logic */ /* business logic */ /* business logic */ free(f); free(b); free(z); return SUCCESS; You really don't want to repeat yourself like that, you'd be liable to forget something. Now we could use `goto` and a return value: ReturnValue retval = SUCCESS; Foo f = new_foo(); if (!f) { retval = FAIL_FOO; goto cleanup; } Bar b = new_bar(); if (!b) { retval FAIL_BAR; goto cleanup; } Baz z = new_baz(); if (!z) { retval FAIL_BAZ; goto cleanup; } /* business logic */ /* business logic */ /* business logic */ cleanup: free(f); free(b); free(z); return retval; Better, except maybe the fact that Q/A hates you. All is not lost, you can still please them with a single exit point (pattern seen in the real world): ReturnValue retval = SUCCESS; Foo f = new_foo(); if (f) { Bar b = new_bar(); if (b) { Baz z = new_baz(); if (z) { /* business logic */ /* business logic */ /* business logic */ } else { retval FAIL_BAZ; } free(z); } else { retval FAIL_BAR; } free(b); } else { retval = FAIL_FOO; } free(f); return retval; To be honest this may be the worst of them all. --- The only real contenders for this use case are defer and goto, and even then I think I prefer defer.
- ludocode 6y agoI think this example demonstrates the opposite of what you intended. Defer doesn't give you anything that you can't already do. In your last four code blocks, you demonstrated three alternate ways to implement the functionality you want without defer. Defer is just a slightly different, arguably nicer fourth way to implement the same code. Language features should be orthogonal. A new language feature should add something that is not possible or extremely painful to do with the existing language features. I just don't see how these minor syntax adjustments warrant a new feature, especially one with as much complexity and corner cases as this defer proposal. (The real answer to "why defer?", of course, is that the authors need it to implement panic/recover. This proposal should stop masquerading as a defer mechanism for C and instead call itself what it really is: exceptions for C.)
- loup-vaillant 6y ago> In your last four code blocks, you demonstrated three alternate ways to implement the functionality you want without defer. My, I didn't think it was possible to miss the point like that. Are you even arguing in good faith? Let's examine for a moment the 3 other alternatives. First, we get the "repeat ourselves" problem: when I have several exit points, I must clean up at each exit. And if I edit the code in any way, (for instance by adding yet another check), I must review everything that has been initialised until this point and clean it up there again. This might be okay if I have only 1 or 2 exit points, but if I have more this is clearly unacceptable. Second, we have goto. We replace our exit points by a goto cleanup. That one at least can scale. I don't like it however for three reasons. First, the cleanup code is at the end, far from the init code, so checking that the two pairs together correctly is inconvenient. Second, I need to manage an additional variable for the return value. Third, goto is banned in a lot of places, no matter how convoluted the alternatives may be. Third, we have this monstrous pyramid if else that wastes horizontal space, requires you to re-indent everything at the slightest edit, separates cleanup code from init code, and is just plain ugly. The only thing going for it is the single exit point, and frankly it isn't much. --- Those "alternatives" are anything but. They're what we have to do when faced with a limited language that doesn't express what we want to say. Workarounds, not solutions. > minor syntax adjustments Your perspective must be seriously warped if you're calling the function-wide reorganisation I spoke of "minor syntax adjustments". Or you're not arguing in good faith. > The real answer to "why defer?", of course, is that the authors need it to implement panic/recover. That is a separate point, which I think I agree with. Me, I just want a way to trigger an instruction when we exit the current scope. It's the necessary complement to `break` and `return`, which provide ways to exit scope before the end of the block. We could get rid of them, and apply a straightjacket structured programming discipline of course, but personally, I don't think I'm ready to give up on `break` and `return`.
- scott_s 6y agoThe main use case is not forgetting to free resources. It's when you do have resource-freeing code, but it's tricky to make sure you free only the resources you know for sure you have already allocated. This is common in the Linux kernel. A great example is this function I found at random from fork.c: https://github.com/torvalds/linux/blob/master/kernel/fork.c#L469 https://github.com/torvalds/linux/blob/master/kernel/fork.c#... Notice that there are five different goto targets, each for a specific case of what has-and-has-not been allocated. The resources are memory, locks, and even TLB flushing. This code would probably be cleaner with defer.
- swiley 6y agoIt's not so bad if you obsess over valgrind warnings.
- asveikau 6y agoThat solution doesn't work if you malloc up a structure and stick other things that need cleanup and won't be aware of the context (example: a file descriptor) inside.
- sesuximo 6y agoMaybe, maybe not. If your context has a list of things to free, it can also have a list of things to close. Or whatever you want. Many libraries do stuff like this and call the context creation/deletion lib_init/ lib_shutdown.
- asveikau 6y agoIt sounds an awful lot like such a context mechanism needs dynamic allocations. Something like the GNU cleanup attribute or a proposal like defer, or C++ RAII, can do that as a stack unwind, without the need for heap.
- sesuximo 6y agoThat would be the easiest way. Definitely a disadvantage