5 ms·
If you're willing to use macros, this can do the trick. Return and break will preempt the expression, but continue should work fine. #define DEFER(EXPR) for(
by skolskoly 7y ago
If you're willing to use macros, this can do the trick.
Return and break will preempt the expression, but continue should work fine.
#define DEFER(EXPR) for(int _tmp=1; _tmp; _tmp=0,(EXPR))
Example:
char * data = malloc(32);
DEFER(free(data))
{
// do stuff
}
- jonas21 7y agoOh, that's pure evil/genius.
- BubRoss 7y agoThat is really clever, I don't know why I never thought about the for loop being a way to run an expression after a scope ends.
- adtac 7y agoPerhaps I'm misunderstanding this, but this doesn't solve the problem at all -- returning within the block statement wouldn't call the deferred expression, which is entirely the point of a defer statement, no? If control is guaranteed to reach the end of the block statement, of course; but requiring such a constraint would make this defer very handicapped. You have tons of function exit points and you most likely want to free memory at every single one.
- seventh-chord 7y agoRegarding having tons of exit points, I find it actually adds no more cognitive load to use only a single return per function, compared to having multiple returns and keeping resource-freeing up-to-date for each. Most of the challenge in writing code with a single return seems to come from the fact that I'm not used to doing it. Of course, sometimes it leads to heavily nested control flow, although I'm not sure yet whether or not I mind that. It does certainly simplify reasoning about control flow though, especially in large functions. That all being said, I'd still rather just have defer.
- simias 7y agoWhat you say makes sense but I'd advise beginners reading this comment not to overdo it. I've seen new coders "cargo culting" this one-return-per-function rule and leading to code that was, in my opinion, unnecessarily complicated. For instance parameter validation is one instance where I think an early return is very much warranted. There you typically have no cleanup to do since it's effectively the prelude of the function. Regarding nested error handling, I prefer the "goto fail" pattern. Some people are opposed to the use of goto as a matter of principle but I think in this case it's fine. It's effectively the poor man's RAII when you don't have destructors.
- Gibbon1 7y agoNot unreasonable for functions to have guard returns, a 'happy' return. And a goto fail return. That's decent pattern when you have a function that handles modifying state.
- augustk 7y ago> For instance parameter validation is one instance where I think an early return is very much warranted. There you typically have no cleanup to do since it's effectively the prelude of the function. That's what the assert statement is for; checking pre- and postconditions.
- simias 7y agoYes, the only way I could imagine implementing a "proper" defer in C would be to use (like for most insane C hacks) setjmp/longjmp. I'm fairly sure it's explicitly forbidden by the Geneva Conventions though. Alternatively you might be able to use nested functions to guard against a stray return but that's not standard.
- saagarjha 7y agoHow would you implement defer with setjmp/longjmp? I can’t think of anything off the top of my head.
- simias 7y agoYou'd probably have to #define "return" to call longjmp instead. Although I guess at this point you might be better off just have return call the cleanup code. You'd have to be careful to handle the nesting correctly though, which might be slightly easier with setjmp contexts.
- Someone 7y agoI guess you could do something like (couldn’t get Xcode’s clang to compile this, so it probably is buggy) static unsigned int __deferDepth = 0; #define RETURN return #define return ((__deferDepth == 0) ? RETURN : break do_return) #define DEFER(EXPR) \ __label__ do_return; \ for(int _tmp = 1; _tmp; _tmp = (do { \ __deferDepth += 1; \ EXPR; \ __deferDepth -= 1; \ break; \ do_return: \ RETURN; \ }while(0);)) Nested DEFERs could get hairy, though (do you need a stack of __returnNotBreak values?), and there probably are issues if EXPR contains some nested loops with break or return. Regardless, this isn’t C. Statement expressions (and __label_) are a gcc* extension, and if you’re willing to go there, __attribute__((cleanup)) seems the wiser route.