9 ms·
C: Simple Defer, Ready to Use
- andrewmcwatters 2y agoI know you can do these things, but I've always tried to avoid macro programming all together in C and C++ unless forced to use them by dependencies in idiomatic usage.
- stefanos82 2y agoThis should have been in C ages ago, but better late than never!
- cmovq 2y agoThis isn't new. The only C23 feature this article uses is the attribute syntax, but you could just use `__attribute((cleanup(F)))` instead.
- robertlagrant 2y agoTis a joke my friend.
- marmaduke 2y agoi like how the contraction it’s and abbreviation T’is are anagrams
- sigzero 2y agoIt is not T'is, it is Tis. No apostrophe.
- hollerith 2y agoI've only ever seen it spelled 'tis
- dkjaudyeqooe 2y agoIt's a contraction of "it is" so " 'tis " is correct.
- jcranmer 2y agoThe apostrophe is in the wrong spot, but 'tis is the correct spelling, and the only one I've ever seen.
- IncRnd 2y agoThe word is 'tis with an apostrophe. https://www.britannica.com/dictionary/%27tis https://www.britannica.com/dictionary/%27tis https://www.merriam-webster.com/dictionary/%27tis https://www.merriam-webster.com/dictionary/%27tis https://dictionary.cambridge.org/us/dictionary/english/tis https://dictionary.cambridge.org/us/dictionary/english/tis
- webstrand 2y agoVery nice, using the goto trick to perform cleanups always felt dirty to me.
- foooorsyth 2y agogotos can be used without shame. Dijkstra was wrong (in this rare case). defer is cleaner, though.
- jerf 2y agoDijkstra was not wrong. Modern programmers are wrong in thinking that the goto that they use is what Dijkstra was talking about, merely because of the fact it happens to be called the same thing. I mean, I get how that can happen, no sarcasm, but the goto Dijkstra was talking about and what is in a modern language is not the same. https://jerf.org/iri/post/2024/goto/ https://jerf.org/iri/post/2024/goto/ The goto Dijkstra is talking about is dead. It lives only in assembler. Even BASIC doesn't have it anymore in any modern variant. Modern programmers do not need to live in fear of modern goto because of how awful it was literally 50 years ago.
- Spivak 2y agoOr the tl;dr in modern parlance Dijkstra was railing against the evils of setjmp.
- ryao 2y agoLua uses it for error handling. It is really hard to understand the lua code. :/
- zzo38computer 2y agoSometimes setjmp is useful and I had occasionally used it, but usually it is not needed. There is certain considerations you must make in order to be careful when you are using setjmp though. (Free Hero Mesh uses setjmp in the execute_turn function. This function will call several other functions some of which are recursive, and sometimes an error occurs or WinLevel or LoseLevel occurs (even though these aren't errors), in which case it will have to return immediately. I did try to ensure that this use will not result in memory leaks or other problems; e.g. v_set_popup allocates a string and will not call longjmp while the string is allocated, until it has been assigned to a global variable (in which case the cleanup functions will handle this). Furthermore, the error messages are always static, so it is not necessary to handle the memory management of that either.)
- loeg 2y agoDo defers get processed if an exception is thrown? I know C doesn’t have exceptions, but real world C code often interacts with C++ code. So it is necessary imo to define that interaction. In C++ we have something pretty similar already in the form of Folly ScopeGuard (SCOPE_EXIT {}).
- kevin_thibedeau 2y agoGCC has the __cleanup__ attribute which works in conjunction with exceptions in C to provide an RAII mechanism.
- eqvinox 2y agoNo it doesn't; C functions don't set up appropriate registrations with the C++ exception / stack unwinding systems, thus C functions are simply skipped over on the way up towards the nearest exception handler. __attribute__((cleanup(…))) is purely a scope-local mechanism, it has absolutely nothing to do with exceptions.
- leni536 2y ago"If -fexceptions is enabled, then cleanup_function is run during the stack unwinding that happens during the processing of the exception. Note that the cleanup attribute does not allow the exception to be caught, only to perform an action. It is undefined what happens if cleanup_function does not return normally."[1] While it's true that it -fexceptions is disabled for C by default, some C libraries need to enable it anyway if they want to interact with C++ exceptions this way. For example C++ requires that qsort and bsearch propagate the exception thrown by the comparison callback normally, so libc implementations that are also used from C++ do enable it. [1] https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute...
- eqvinox 2y agoAh, I forgot about -fexceptions, Thanks. Though I need to point out it's non-default and rarely used, and in particular: > some C libraries need to enable it anyway if they want to interact with C++ exceptions this way. For example C++ requires that qsort and bsearch propagate the exception thrown by the comparison callback normally -fexceptions is not needed for this, C++ exceptions will just transparently bubble through C functions without its use. I don't think I know of any project using -fexceptions… time to google…
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- accelbred 2y agoUnfortunately, this requires an executable stack, and only works on gcc, though an alternate implementation can work on clang. After a few attempts at defer, I ended up using a cleanup macro that just takes a function and a value to pass to it: https://github.com/aws-greengrass/aws-greengrass-lite/blob/8fea9e7dae75e20e026b42345a2347dd424371f8/ggl-lib/include/ggl/cleanup.h#L15 https://github.com/aws-greengrass/aws-greengrass-lite/blob/8... Since the attribute or a function-like macro in the attribute position broke the c parsing in some tooling, I made the macro look like a statement.
- uecker 2y agoIt does not need an executable stack: https://godbolt.org/z/K1GTa4jh4 https://godbolt.org/z/K1GTa4jh4
- accelbred 2y agoThe macro from the article uses nested functions which on gcc are implemented with tramolines that need executable stack. https://thephd.dev/lambdas-nested-functions-block-expressions-oh-my https://thephd.dev/lambdas-nested-functions-block-expression...
- uecker 2y agoThe trampolines are not generated in this case.
- eqvinox 2y agoTo be fair you're not communicating very well — there are no trampolines being used here because the call site is directly in the same function, so no trampoline is needed with already being in the correct stack frame. (And also on -O1 and higher the entire call is optimized out and the nested function inlined instead.)
- uecker 2y ago
- Night_Thastus 2y agoI really, really do not like what that could do to the readability of code if it was used liberally. It's like GOTOs, but worse, because it's not as visible. C++'s destructors feel like a better/more explicit way to handle these sorts of problems.
- skinner927 2y agoBut thats C++
- Night_Thastus 2y agoI am aware. I'm just pointing it out because they're similar ideas.
- accelbred 2y agoDestructors are hidden control flow, and can be non-obvious for structs from other files. I find they make code significantly harder to follow than in plain C. Defer does not have the problem, as all the logic in your function is explicitly there.
- quelsolaar 2y agoNot right there, some other place in the function. Also people will start adding defer in macros and then things will go sideways.
- jefbyokyie 2y ago> Not right there, some other place in the function. Exactly. Both C++ RAII (constructors/destructors) and C23 defer are awful. They make the behavior implicit; in effect they tie an indefinitely large (and growing) baggage to scope termination without the source code reflecting that baggage. Cascading gotos (or the arrow pattern) are much better. Cleanup code execution is clearly reflected by location in the source code; the exit path can be easily stepped through in a debugger.
- 2y ago
- tuveson 2y agoFor parity, C should add a `prefer` keyword that hoists statements to the top of the function.
- rurban 2y agoNo, C is not perl. They do have BEGIN blocks, but these are constexpr.
- eru 2y agoYou have top and bottom, but what about hoisting sideways?
- shakna 2y agoThat seems actually feasible, with instruction scheduling. But would probably ruin a lot of the memory alignment optimisations.
- e12e 2y agoSounds like came_from might be helpful in implementing prefer?
- fuhsnn 2y agoThe C++ lambda version can be a foot gun: https://godbolt.org/z/Wd66GcrdG https://godbolt.org/z/Wd66GcrdG, if the return value is a struct, NRVO (named return value optimization) may be applied and the lambda will be called in different order. As for the n3434 proposal, given that the listed implementation experiences are all macro-based, wouldn't it be more easily adopted if proposed as a standard macro like <stdarg.h>?
- funny_falcon 2y agoOMG! returns_struct looks actually correct to me, ie it is expected (by me). Golang's defer works this way. Do both examples follow standard? Or is it common misinterpretation by all compilers?
- 1oooqooq 2y agotemplate<typename T> struct __df_st : T { [[gnu::always_inline]] inline __df_st(T g) : T(g) { // empty } [[gnu::always_inline]] inline ~__df_st() { T::operator()(); } }; #define __DEFER__(V) __df_st const V = [&](void)->void #define defer __DEFER(__COUNTER__) #define __DEFER(N) __DEFER_(N) #define __DEFER_(N) __DEFER__(__DEFER_VARIABLE_ ## N) #include <stdio.h> struct S { int r; ~S(){} }; (i know hn will mangle this but i won't indent this on mobile...) people really write cpp like this or is this a intentionally obscure example?
- fuhsnn 2y agoThat's from the OP blog, the intention of those macros is to generate uniquely named local lambda. Cleaner version is like: https://github.com/llvm/llvm-project/issues/100869#issue-2433451886 https://github.com/llvm/llvm-project/issues/100869#issue-243...
- worik 2y agoDoes C really need this? Do languages need to grow in this way? The overriding virtue of C is simplicity.
- vkazanov 2y agoYes it does, and numerous projects already use variants of this trick.
- fuhsnn 2y agoIncluding Linux kernel https://github.com/torvalds/linux/blob/master/include/linux/cleanup.h https://github.com/torvalds/linux/blob/master/include/linux/...
- pjmlp 2y agoThat was lost long time ago, after K&R C became part of WG14.
- ahoka 2y agoC semantics are far from simple.
- cryptonector 2y ago> Does C really need this? Yes. > Do languages need to grow in this way? Yes. > The overriding virtue of C is simplicity. C is not simple. It used to be, but it's not been for a long time.
- cassepipe 2y agoAbout your last point : https://queue.acm.org/detail.cfm?id=3212479 https://queue.acm.org/detail.cfm?id=3212479
- jefbyokyie 2y ago> Does C really need this? No. > Do languages need to grow in this way? No. > The overriding virtue of C is simplicity. It's no longer simple, especially not with the C11 memory model (which got retrofitted from C++11, and is totally incomprehensible without reading multiple hundreds of pages of PhD dissertations); however, gratuitously complicating C shouldn't be done.
- eqvinox 2y agoUnfortunately, nested functions are one of the few GCC extensions not implemented by clang, as such this really only works on GCC.
- loeg 2y agoIf this proposal is adopted in C2Y, surely Clang will implement it.
- pjmlp 2y agoMost likely yes, although being on ISO doesn't mean much, there are plenty of examples of features that not every compiler fully supports.
- pkhuong 2y agoI've used blocks with a dummy RTL and extracted the callback from the block descriptor... it worked.
- bangaladore 2y agoI'm a strong believer that if C had defer, many bugs would disappear. The number 1 issue I find myself having when switching from C++ to C is missing RAII (Main C++ way of implementing defer).
- pjmlp 2y agoThe major source of bugs in C is located on string.h, and nothing has been made in 50 years to fix that. Really fix, not mitigations with their own gotchas.
- Dwedit 2y agoThere have been things done to try to fix that, see "strcpy_s" and other related functions. Visual Studio even considers use of the classic string functions (like "strcpy") to be a compiler error. You need to define a specific macro before you are allowed to use them.
- zwieback 2y agoI agree about missing RAII when switching away from C++ but it seems like defer cleans up after enclosing function exits while RAII cleans up when object goes out of scope, which is more fine-grained. Maybe I'm misunderstanding exactly when defer would clean up but it seems more like a safety feature. As people pile more stuff into the function the cleanup would be deferred more and more while RAII encapsulates the management right where you need it, e.g. exiting a loop or something.
- jagrsw 2y agoAges old (~2014 IIRC) defer implementation for gcc and for clang: https://github.com/google/honggfuzz/blob/c549b4c31815e170d3bfa3a76eabe20d3c837cda/libhfcommon/util.h#L36 https://github.com/google/honggfuzz/blob/c549b4c31815e170d3b...
- joshmarinacci 2y agoUnfortunately the first thing I see is a giant ad scroll down over the header blocking the entire viewport on my phone, so immediately hit the back button. Are all personal blogs automatically monetized now?
- wgjordan 2y agoFree sites on WordPress.com are: https://wordpress.com/support/no-ads/ https://wordpress.com/support/no-ads/
- senderista 2y agoFor C++, scope_guard has been around forever.
- dataflow 2y agoWhat are the unwind semantics on GCC, Clang, MSVC?
- deleted 2y ago[deleted]
- aleden 2y agoNobody knows about BOOST_SCOPE_DEFER? #include <boost/scope/defer.hpp> BOOST_SCOPE_DEFER [&] { close(fd); }; Implementation here: https://github.com/boostorg/scope/blob/develop/include/boost/scope/defer.hpp https://github.com/boostorg/scope/blob/develop/include/boost...
- btown 2y agoI once worked on a student robotics project where if you didn't gracefully shut down the connection to a specialized camera, it had a significant chance of physically bricking the camera. We were using C++ and essentially instrumented code review processes, despite being a student group, to ensure nothing was ever called in a way where the destructor wouldn't be called - and broke out vision processing into a separate process so if other processes crashed we'd still be okay. In retrospect it was amazing training for a software engineering career. But I always look at the words "simple" and "defer" and shudder when I see them next to each other! Just like you'd have a "threat model" for cybersecurity, make sure you understand the consequences of a defer/destructor implementation not functioning properly.
- xorvoid 2y agoYikes. So the OOM Killer bricks your camera then!?!
- btown 2y agoJust (carefully) power down the robot before the memory leaks exceed available RAM!
- gizmo686 2y agoYikes. What is it with high school robotics and buggy camera APIs? Back when I competed, we were using Java with the FRC provided vision library. When we tried adding the camera, we discovered that our Java code started to segfault after a few minutes of running. This turned out to be comming from the native camera code being invoked through the FFI. This wasn't as bad as what you saw, as a restart would fix it (for a few minutes at least), but would disable us for the rest of a match if it ever happened at a competition. At the end of the day, we worked around this by implementing a null pointer check. It was something along the lines if: if (camera.toString().contains("(null)") return; I assume there was still a race condition where the garbage collector would trigger whatever ffi destructor nulled out the relevent pointers; but we never ran into it. To your point, this looks to be an alternative to the 'goto end' pattern in C. In my experience that is the least bug prone pattern of destructors for C code.
- lilyball 2y agoI don't think "you can implement this slightly modified version of the proposal in GCC macros" is a good reason to change the defer proposal. It doesn't work in clang¹ or any other non-GCC C implementation, a trailing semicolon after the close brace is an ugly wart, and saying "if you want to implement this all you have to do is implement [[gnu::cleanup]] and nested local functions" is presumably much more work for other C implementations than just implementing defer is. I also question whether this even works in GCC with executable stacks disabled (I know GCC can optimize away the trampoline since the function doesn't escape the local scope, but does GCC let you write nested functions at all without executable stacks?). ¹The author says it works in clang using Apple's Blocks feature, but Blocks should not be required for defer and the variable semantics are wrong so it's a non-starter.
- kenferry 2y agoI don't think they're advocating not doing defer in C? They're saying you can backport the functionality if needed, or if you want to start using it now.
- lilyball 2y agoThey're recommending changes to the proposal though, such as requiring a trailing semicolon after the close brace. It also changes the syntactical category of the defer statement, though it's not clear to me what that actually affects.
- eru 2y agoWhy do you think you need executable stacks for nested functions? I don't know what GCC is doing, but functional programming languages usually 'compile away' the nesting of functions fairly early, see https://en.wikipedia.org/wiki/Lambda_lifting https://en.wikipedia.org/wiki/Lambda_lifting Update: Oh, I see, it's because GCC doesn't want to change how function are passed around ('function pointers' in C speak), so they need to get creative. Functional languages use something closer to what C people would call a 'fat pointer' to fix this.
- 2y ago
- teo_zero 2y agoThe macro implementation requires braces. Even when the deferred action is a single statement you can't use this: defer fclose(f); Not a serious problem, just inelegant.
- antics 2y agoOne big flaw in Go-style `defer` is that error handling and scoping are an afterthought, and it's disappointing to see this is not improved upon in this proposal (at least as I understand the proposal). I do think this matters in practice. If I have a `func whatever() error` IME it's a common to accidentally do something like `defer whatever()` without catching handling the error. To work around that you'd need to do something like the following. var err error defer func() { err = whatever() } For me personally: ugh. I understand the "received wisdom" is to just structure your code differently. I don't think that's reasonable, because cleanup code is often complicated, often can fail, and is generally vastly less well-tested/exercised. YMMV, my experience is that, because of these things, the dispose-path bugs tend to be disproportionately bad. Error handling facilities that treat the dispose path as by-default not worth insuring are IMO setting users up to have a very bad time later. While we're fixing up the error handling, I really think it's not an accident that "`defer` in a loop does not terminate in the loop, but at the end of the function" is such a frequent misunderstanding. Yes the docs are clear, but all languages spend a lot of time training you on lexical scoping, and lexically-defined disposal lifetimes (e.g., C#'s `using` or Python's `with`) are a natural extension of that training. Those implementations to varying degrees have the same error handling problem, but with some effort I really think a better world is possible, and I don't think we have to expand this to full-on RAII to provide such semantics.
- accelbred 2y ago__attribute__((cleanup)) cleans up on scope exit, unlike go.
- pjmlp 2y ago> One big flaw in Go-style `defer` is that error handling and scoping are an afterthought.... Like many other things in Go's approach to language design, still better than using plain old C, though.
- antics 2y agoYes, for better or for worse, I think the ethos of Go is pretty much "a mulligan on C". Every C project re-implements a bespoke hash table, struggles with Unicode strings, spends a lot of time on memory management, gets tripped up on dynamic linking, and so on. If otherwise like C but are tired of those things, guess which language is perfect for you?
- seodisparate 2y agoOne can use C's `cleanup` attribute like a defer. https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute... The function signature is `void(MyType*)` if it is used like: __attribute__((cleanup(MyTypeCleanupFunction))) MyType myType; If it's a pointer, it'll call the cleanup as a double ptr, and etc. Note that the specified cleanup function can do anything you want to, though the intended purpose is to cleanup the variable when it goes out of scope.
- 0xTJ 2y agoThat isn't a C feature, it's a feature of GCC and other compilers. While I have used (and horribly overused) `cleanup` in personal projects, it's not a replacement for a feature being in the standard.
- wakawaka28 2y agoI think what you really want in C++ is ScopeGuard. It is an old idea but it has been improved in recent versions of C++.
- LelouBil 2y agoI'm still not sure which is more readable between defer and try/finally (not in any language particularly). I know I hate one and love the other, but I still don't know which.
- EPWN3D 2y agoI like the approach of attributing variables better to be honest, e.g. Apple's Libc has a header with attributes for some common resource types. https://github.com/apple-oss-distributions/Libc/blob/Libc-1669.40.2/libdarwin/h/cleanup.h https://github.com/apple-oss-distributions/Libc/blob/Libc-16...
- gnuhigh 2y agoYou can implement defer using computed goto or longjmp. Problem is that it doesn't work if user returns early so you need a custom return macro.
- Ericson2314 2y agoEveryone that doesn't like a new standardized defer is doing https://knowyourmeme.com/memes/no-take-only-throw https://knowyourmeme.com/memes/no-take-only-throw but as "no forest, only trees" The explicit alternative for defer is an unreadable mess. And guess what? If you really need to see it, we should having tooling that desugars the defer to gotos. That's the best of both worlds! - The "master" source code is high level "say what I mean" - Low level view that is still much higher than dropping down into assembly still exists! You really should be able to express the required cleanup semantics with "defer", and if you cannot, you can always just replace the original with the low level view and edit it instead --- good luck getting that past code review, however :).
- jefbyokyie 2y ago> You really should be able to express the required cleanup semantics with "defer" I'm perfectly able to do that, I just don't want it, because the result is terrible. The syntax no longer shows what happens when and where. And, if you look at n3434 <https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3434.htm https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3434.htm>, you can see the following gem: > the deferred block may itself contain other defer blocks, that are then executed in chain according to the same rules This is the worst possible outcome. It means that a continuation passing style sub-language gets embedded in C. { defer { a(); defer { b(); }; c(); }; defer { d(); }; } At the final closing brace shown, d() will be invoked first, then a(), then c(), then finally b(). Syntactically, the invocations appear in a-b-c-d order in the source code, but the actual execution is neither that nor the inverse d-c-b-a nor the single-level inverse order d-a-b-c. THAT is an unreadable mess. Good luck debugging that. Not dissimilar to chaining futures in modern async C++ (lambdas deeply nested in lambdas). Which is an abomination. Your source code syntax is now completely detached from the execution order within a single thread. Good luck getting that past code review. Your reference to "no take only throw" makes no sense to me. The gist of that meme is "various characters making contradictory demands". Nobody is making demands here. There's no need for an "explicit alternative". Just don't defer at all, regardless of style. Write the error path / exit path explicitly, using gotos or the arrow pattern.
- 2y ago
- nathanmills 2y agoThis is completely unnecessary. Just use a goto statement with labels. The whole point of C is that it's a simple language, and adding more complexity like 'defer' just makes it harder to understand. This proposal is just making C more like those bloated modern languages. If you need fancy features like this, use Rust instead.