11 ms·
Defer available in gcc and clang
- kjgkjhfkjf 8mo agoThe article is a bit dense, but what it's announcing is effectively golang's `defer` (with extra braces) or a limited form of C++'s RAII (with much less boilerplate). Both RAII and `defer` have proven to be highly useful in real-world code. This seems like a good addition to the C language that I hope makes it into the standard.
- Zambyte 8mo agoProbably closer to defer in Zig than in Go, I would imagine. Defer in Go executes when the function deferred within returns; defer in Zig executes when the scope deferred within exits.
- rwmj 8mo agoThis is the crucial difference. Scope-based is much better. By the way, GCC and Clang have attribute((cleanup)) (which is the same, scope-based clean-up) and have done for over a decade, and this is widely used in open source projects now.
- bashkiddie 8mo agoI would like to second this. In Golang if you iterate over a thousand files and defer File.close() your OS will run out of file descriptors
- Joker_vD 8mo agoWell, unless you're on Windows :D Even on Windows XP Home Edition I could open a million file handles with no problems. Seriously, why is default ulimit on file descriptors on Linux measly 1024?
- nasretdinov 8mo agoSome system calls like select() will not work if there are more than 1024 FDs open (https://man7.org/linux/man-pages/man2/select.2.html https://man7.org/linux/man-pages/man2/select.2.html), so it probably (?) makes sense to default to it. Although I don't really think that in 2k26 it makes sense to have such a low limit on desktops, that is true.
- remexre 8mo agohttps://0pointer.net/blog/file-descriptor-limits.html https://0pointer.net/blog/file-descriptor-limits.html
- CodesInChaos 8mo agoI wonder what the thought process of the Go designers was when coming up with that approach. Function scope is rarely what a user needs, has major pitfalls, and is more complex to implement in the compiler (need to append to an unbounded list).
- mort96 8mo agoI hate that you can't call defer in a loop. I hate even more that you can call defer in a loop, and it will appear to work, as long as the loop has relatively few iterations, and is just silently massively wasteful.
- usrnm 8mo agoThe go way of dealing with it is wrapping the block with your defers in a lambda. Looks weird at first, but you can get used to it.
- mort96 8mo agoI know. Or in some cases, you can put the loop body in a dedicated function. There are workarounds. It's just bad that the wrong way a) is the most obvious way, and b) is silently wrong in such a way that it appears to work during testing, often becoming a problem only when confronted with real-world data, and often surfacing only as being a hard-to-debug performance or resource usage issue.
- deleted 8mo ago[deleted]
- 9rx 8mo agoWhat's the use-case for block-level defer? In a tight loop you'd want your cleanup to happen after the fact. And in, say, an IO loop, you're going to want concurrency anyway, which necessarily introduces new function scope.
- 8mo ago
- jibal 8mo agodefer was invented by Andrei Alexandrescu who spelled it scope(exit)/scope(failure) [Zig's errdefer]/scope(success) ... it first appeared in D 2.0 after Andrei convinced Walter Bright to add it.
- L-4 8mo agoBoth defer and RAII have proven to be useful, but RAII has also proven to be quite harmful in cases, in the limit introducing a lot of hidden control flow. I think that defer is actually limited in ways that are good - I don't see it introducing surprising control flow in the same way.
- fauigerzigerk 8mo agoBut of course what you call "surprising" and "hidden" is also RAII's strength. It allows library authors to take responsibility for cleaning up resources in exactly one place rather than forcing library users to insert a defer call in every single place the library is used.
- gpderetta 8mo agoRAII also composes.
- kibwen 8mo agoDefer is also hidden control flow. At the end of every block, you need to read backwards in the entire block to see if a defer was declared in order to determine where control will jump to. Please stop pretending that defer isn't hidden control flow. > RAII has also proven to be quite harmful in cases The downsides of defer are much worse than the "downsides" of RAII. Defer is manual and error-prone, something that you have to remember to do every single time.
- sparkie 8mo agoDefer is a restricted form of COMEFROM with automatic labels. You COMEFROM the end of the next `defer` block in the same scope, or from the end of the function (before `return`) if there is no more `defer`. The order of execution of defer-blocks is backwards (bottom-to-top) rather than the typical top-to-bottom. puts("foo"); defer { puts("bar"); } puts("baz"); defer { puts("qux"); } puts("corge"); return; Will evaluate: puts("foo"); puts("baz"); puts("corge"); puts("qux"); puts("bar"); return;
- throwaway27448 8mo agoThis certainly isn't RAII—the term is quite literal, Resource Acquisition Is Initialization, rather than calling code as the scope exits. This is the latter of course, not the former.
- mort96 8mo agoPeople often say that "RAII" is kind of a misnomer; the real power of RAII is deterministic destruction. And I agree with this sentiment; resource acquisition is the boring part of RAII, deterministic destruction is where the utility comes from. In that sense, there's a clear analogy between RAII and defer. But yeah, RAII can only provide deterministic destruction because resource acquisition is initialization. As long as resource acquisition is decoupled from initialization, you need to manually track whether a variable has been initialized or not, and make sure to only call a destruction function (be that by putting free() before a return or through 'defer my_type_destroy(my_var)') in the paths where you know that your variable is initialized. So "A limited form of RAII" is probably the wrong way to think about it.
- throwaway27448 8mo ago> and make sure to...call a destruction function Which removes half the value of RAII as I see it—needing when and to know how to unacquire the resource is half the battle, a burden that using RAII removes. Of course, calling code as the scope exits is still useful. It just seems silly to call it any form of RAII.
- usrnm 8mo agoIn my opinion, it's the initialization part of RAII which is really powerful and still missing from most other languages. When implemented properly, RAII completely eliminates a whole class of bugs related to uninitialized or partially initialized objects: if all initialization happens during construction, then you either have a fully initialized correct object, or you exit via an exception, no third state. Additionaly, tying resources to constructors makes the correct order of freeing these resources automatic. If you consume all your dependencies during construction, then destructors just walk the dependency graph in the correct order without you even thinking about it. Agreed, that writing your code like this requires some getting used to and isn't even always possible, but it's still a very powerful idea that goes beyond simple automatic destruction
- usrnm 8mo agoTo be fair, RAII is so much more than just automatic cleanup. It's a shame how misunderstood this idea has become over the years
- randusername 8mo agoCan you share some sources that give a more complete overview of it? I got out my 4e Stroustrup book and checked the index, RAII only comes up when discussing resource management. Interestingly, the verbatim introduction to RAII given is: > ... RAII allows us to eliminate "naked new operations," that is, to avoid allocations in general code and keep them buried inside the implementation of well-behaved abstractions. Similarly "naked delete" operations should be avoided. Avoiding naked new and naked delete makes code far less error-prone and far easier to keep free of resource leaks From the embedded standpoint, and after working with Zig a bit, I'm not convinced about that last line. Hiding heap allocations seems like it make it harder to avoid resource leaks!
- xerokimo 7mo ago> Hiding heap allocations seems like it make it harder to avoid resource leaks! Because types come in constructor / destructor pairs. When creating variables, you're forced to invoke a constructor, and when an the object's lifetime ends, the compiler will insert a destructor call for you. If you allocate on construction and de-allocate on destruction, it'll be very hard for the leak to happen because you can't forget to call the destructor
- omoikane 8mo ago> with extra braces The extra braces appear to be optional according to the examples in https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf (see pages 13-14)
- babalark 8mo agoYes!! One step closer to having defer in the standard. Related blog post from last year: https://thephd.dev/c2y-the-defer-technical-specification-its-time-go-go-go https://thephd.dev/c2y-the-defer-technical-specification-its... (https://news.ycombinator.com/item?id=43379265 https://news.ycombinator.com/item?id=43379265)
- LexiMax 8mo agoA long overdue feature. Though I do wonder what the chances are that the C subset of C++ will ever add this feature. I use my own homespun "scope exit" which runs a lambda in a destructor quite a bit, but every time I use it I wish I could just "defer" instead.
- surajrmal 8mo agoIn many cases that's preferred as you want the ability to cancel the deferred lambda.
- anilakar 8mo agoVarious macro tricks have existed for a long time but nobody has been able to wrap the return statement yet. The lack of RAII-style automatic cleanups was one of the root causes for the legendary goto fail;[1] bug. [1] https://gotofail.com/ https://gotofail.com/
- uecker 8mo agoI do not see how defer would have helped in this case.
- Davidbrcz 8mo agoPeople manually doing resource cleanup by using goto. I'm assuming that using defer would have prevented the gotos in the first case, and the bug.
- mort96 8mo agoIs that true though? Using defer, the code would be: if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) return err; return err; This has the exact same bug: the function exits with a successful return code as long as the SHA hash update succeeds, skipping further certificate validity checks. The fact that resource cleanup has been relegated to defer so that 'goto fail;' can be replaced with 'return err;' fixes nothing.
- Panzerschrek 8mo agoSuch addition is great. But there is something even better - destructors in C++. Anyone who writes C should consider using C++ instead, where destructors provide a more convenient way for resources freeing.
- alextingle 8mo agoC++ destructors are implicit, while defer is explicit. You can just look at the code in front of you to see what defer is doing. With destructors, you need to know what type you have (not always easy to tell), then find its destructor, and all the destructors of its parent classes, to work out what's going to happen. Sure, if the situation arises frequently, it's nice to be able to design a type that "just works" in C++. But if you need to clean up reliably in just this one place, C++ destructors are a very clunky solution.
- Panzerschrek 8mo agoImplicitness of destructors isn't a problem, it's an advantage - it makes code shorter. Freeing resources in an explicit way creates too much boilerplate and is bug-prone. > With destructors, you need to know what type you have (not always easy to tell), then find its destructor, and all the destructors of its parent classes, to work out what's going to happen Isn't it a code quality issue? It should be clear from class name/description what can happen in its destructor. And if it's not clear, it's not that relevant.
- L-4 8mo ago> Implicitness of destructors isn't a problem It's absolutely a problem. Classically, you spend most of your time reading and debugging code, not writing it. When there's an issue pertaining to RAII, it is hidden away, potentially requiring looking at many subclasses etc.
- mathisfun123 8mo agoi write C++ every day (i actually like it...) but absolutely no one is going to switch from C to C++ just for dtors.
- jonhohle 8mo agoIt’s pedantic, but in the malloc example, I’d put the defer immediately after the assignment. This makes it very obvious that the defer/free goes along with the allocation. It would run regardless of if malloc succeeded or failed, but calling free on a NULL pointer is safe (defined to no-op in the C-spec).
- deleted 8mo ago[deleted]
- flakes 8mo agoI'd say a lot of users are going to borrow patterns from Go, where you'd typically check the error first. resource, err := newResource() if err != nil { return err } defer resource.Close() IMO this pattern makes more sense, as calling exit behavior in most cases won't make sense unless you have acquired the resource in the first place. free may accept a NULL pointer, but it also doesn't need to be called with one either.
- maccard 8mo agoThis example is exactly why RAII is the solution to this problem and not defer.
- miguel_martin 8mo agodefer is literally just an explicit RAII in this example. That is, it's just unnecessary boiler plate to wrap the newResource handle into a struct in this context. In addition, RAII has it's own complexities that need to be dealt with now, i.e. move semantics, which obviously C does not have nor will it likely ever.
- maccard 8mo ago> RAII has it's own complexities that need to be dealt with now, i.e. move semantics, which obviously C does not have nor will it likely ever. In the example above, the question of "do I put defer before or after the `if err != nil` check" is deferred to the programmer. RAII forces you to handle the complexity, defer lets you shoot yourself in the foot.
- userbinator 8mo agoAs others have commented already: if you want to use C++, use C++. I suspect the majority of C programmers neither care nor want stuff like this; I still stay with C89 because I know it will be portable anywhere, and complexities like this are completely at odds with the reason to use C in the first place.
- laserbeam 8mo agoI would say the complexity of implementing defer yourself is a bit annoying for C. However defer itself, as a language feature in a C standard is pretty reasonable. It’s a very straightforward concept and fits well within the scope of C, just as it fit within the scope of zig. As long as it’s the zig defer, not the golang one… I would not introduce zig’s errdeferr though. That one would need additional semantics changes in C to express errors.
- qsera 8mo ago>pretty reasonable It starts out small. Then before you know the language is total shit. Python is a good example. I am observing a very distinguishable phenomenon when internet makes very shallow ideas mainstream and ruin many many good things that stood the test of time. I am not saying this is one of those instances, but what the parent comment makes sense to me. You can see another comment who now wants to go further and want destructors in C. Because of internet, such voices can now reach out to each other, gather and cause a change. But before, such voices would have to go through a lot of sensible heads before they would be able to reach each other. In other words, bad ideas got snuffed early before internet, but now they go mainstream easily. So you see, it starts out slow, but then more and more stuff gets added which diverges more and more from the point.
- gignico 8mo agoI’m just going to start teaching classes of C programming to university first-year CS students. Would you teach `defer` straight away to manage allocated memory?
- leni536 8mo agoIt's still only in a TS, not in ISO C, if that matters.
- zffr 8mo agoMy suggestion is no - first have them do it the hard way. This will help them build the skills to do manual memory management where defer is not available. Once they do learn about defer they will come to appreciate it much more.
- orlp 8mo agoIn university? No, absolutely not straight away. The point of a CS degree is to know the fundamentals of computing, not the latest best practices in programming that abstract the fundamentals.
- jurf 8mo agoMy university also taught best practices alongside that, everytime. I am very grateful for that.
- baq 8mo agoAbsolutely the wrong take. You can teach CS with just pencil and paper, but that doesn’t advance the technology, it might only benefit academia in a narrow sense. CS students should be actively engineering software in addition to doing science.
- flohofwoe 8mo agoNo, but also skip malloc/free until late in the year, and when it comes to heap allocation then don't use example code which allocates and frees single structs, instead introduce concepts like arena allocators to bundle many items with the same max lifetime, pool allocators with generation-counted slots and other memory managements strategies.
- nananana9 8mo agoI took some shit in the comments yesterday for suggesting "you can do it with a few lines of standard C++" to another similar thread, but yet again here we are. Defer takes 10 lines to implement in C++. [1] You don't have to wait 50 years for a committee to introduce basic convenience features, and you don't have to use non-portable extensions until they do (and in this case the __attribute__((cleanup)) has no equivalent in MSVC), if you use a remotely extensible language. [1] https://www.gingerbill.org/article/2015/08/19/defer-in-cpp/ https://www.gingerbill.org/article/2015/08/19/defer-in-cpp/
- mort96 8mo agoWhy is this a relevant comment? We're talking about C, not C++. If you wanted to suggest using an alternative language, you're probably better off recommending Zig: defer takes 0 lines to implement there, and it's closer to C than what C++ is.
- nananana9 8mo agoEveryone reading this (you included) knows full well that unlike Zig/Rust/Odin/whatever, C++ has the special property that you can quite literally* write C code in it, AND you can implement whatever quality of life fixes you need with targeted usage of RAII+templates+macros (defer, bounds checked accesses, result types, whatever). My comment is targeted towards the programmer who is excited about features like this - you can add an extra two characters to your filename and trivially implement those improvements (and more) yourself, without any alterations to your codebase or day to day programming style.
- mort96 8mo agoC in C++ is a pretty terrible experience. The differences your asterisk alludes to are actually quite significant in practice: C++ doesn't let you implicitly cast from void* to other pointer types. This breaks the way you typically heap-allocate variables in C: instead of 'mytype *foo = malloc(sizeof(*foo))', you have to write 'mytype *foo = (mytype *)malloc(sizeof(*foo))'. This adds a non-trivial amount of friction to something you do every handful of lines. String literals in C++ are 'const char *' instead of 'char *'. While this is more technically correct, it means you have to add casts to 'char *' all over the place. Plenty of C APIs are really not very ergonomic when string literals aren't 'char *'. And the big one: C++ doesn't have C's struct literals. Lots of C APIs are super ergonomic if you call them like this: some_function(&(struct params) { .some_param = 10, .other_param = 20, }); You can't do that in C++. C++ has other features (such as its own, different kind of aggregate initialization with designated initializers, and the ability for temporaries to be treated as const references) which make C++ APIs nice to work with from C++, but C APIs based around the assumption that you'll be using struct literals aren't nice to use from C++. If you wanna use C, use C. C is a much better C than C++ is.
- cmovq 8mo agoWould defer be considered hidden control flow? I guess it’s not so hidden since it’s within the same function unlike destructors, exceptions, longjmp.
- fuhsnn 8mo agoIt's one of the most commonly adopted feature among C successor languages (D, Zig, Odin, C3, Hare, Jai); given how opinionated some of them are on these topics, I think it's safe to say it's generally well regarded in PL communities.
- Ygg2 8mo agoWhat I always hated about defer is that you can simply forget or place it in the wrong position. Destructors or linear types (must-use-once types) are a much better solution.
- ZoomZoomZoom 8mo agoIn Nim too, although, the author doesn't like it much.
- Someone 8mo agoIt breaks the idea that statements get executed in the order they appear in the source code, but it ‘only’ moves and sometimes deduplicates (in functions with multiple exit points) statements, it doesn’t hide them. Of course, that idea already isn’t correct in many languages; function arguments are evaluated before a function is called, operator precedence often breaks it, etc, but this moves entire statements, potentially by many lines.
- kibwen 8mo agoYes, defer is absolutely a form of hidden control flow, even if it's less egregious than exceptions.
- jrmg 8mo agoAlways gives me COMEFROM vibes.
- joexbayer 8mo agoA related article discussing Gustedt’s first defer implementation, which also looks at the generated assembly: https://oshub.org/projects/retros-32/posts/defer-resource-cleanup-in-c-with-gccs-magic https://oshub.org/projects/retros-32/posts/defer-resource-cl...
- ozgrakkurt 8mo agoCan somebody explain why this is significantly better than using goto pattern? Genuinely curious as I only have a small amount of experience with c and found goto to be ok so far
- dapperdrake 8mo agoConfer the recent bug related to goto-error handling in OpenSSH where the "additional" error return value wasn’t caught and allowed a security bypass accepting a failed key. Cleanup is good. Jumping around with "goto" confused most people in practice. It seems highly likely that most programmers model "defer" differently in their minds. EDIT: IIRC it was CVE-2025-26465. Read the code and the patch.
- uecker 8mo agoIt is not clear to me that defer helps here. The issue is management of state (the return value) not control flow.
- dapperdrake 8mo agoThe return value depends on control flow ("obvious", please bear with me): With "goto" the cleanup-up can jump anywhere. With "defer" the cleanup cannot really jump anywhere. It is easier to mentally stick to simply cleaning up in a common sense way. And taking care of multiple "unrelated" clean-up steps is "handled for you." (Attacks on this sometimes approach complaints about lack of "common sense".)
- anal_reactor 8mo ago1. Goto pattern is very error-prone. It works until it doesn't and you have a memory leak. The way I solved this issue in my code was a macro that takes a function and creates an object that has said function in its destructor. 2. Defer is mostly useful for C++ code that needs to interact with C API because these two are fundamentally different. C API usually exposes functions "create_something" and "destroy_something", while the C++ pattern is to have an object that has "create_something" hidden inside its constructor, and "destroy_something" inside its destructor.
- sp1rit 8mo agoI quite dislike the defer syntax. IMO the cleanup attribute is the much nicer method of dealing with RAII in C.
- avadodin 8mo agoI think C should be reset to C89 and then go over everything including proposals and accept only the good&compatible bits. If you can't compile K&R, you should label your language "I can't believe it's not C!". I don't have time to learn your esolang.
- unit149 8mo ago[dead]
- bjackman 8mo agoThe Linux kernel has been using __attribute___((cleanup)) for a little while now. So far, I've only seen/used it in cases where the alternative (one goto label) isn't very bad. Even there it's basically welcome. But there are lots of cases in the kernel where we have 10+ goto labels for error paths in complex setup functions. I think when this starts making its way into those areas it will really start having an impact on bugs. Sure, most of those bugs are low impact (it's rare that an attacker can trigger the broken error paths) but still, this is basically free software quality, it would be silly to leave it on the table. And then there's the ACTUAL motivation: it makes the code look nicer.
- t43562 8mo agoIn C I just used goto - you put a cleanup section at the bottom of your code and your error handling just jumps to it. #define RETURN(x) result=x;goto CLEANUP void myfunc() { int result=0; if (commserror()) { RETURN(0); } ..... /* On success */ RETURN(1); CLEANUP: if (myStruct) { free(myStruct); } ... return result } The advantage being that you never have to remember which things are to be freed at which particular error state. The style also avoids lots of nesting because it returns early. It's not as nice as having defer but it does help in larger functions.
- vbezhenar 8mo agoOne small nitpick: you don't need check before `free` call, using `free(NULL)` is fine.
- jagged-chisel 8mo agoBut it does keep one in the habit of using NULL checks.
- david-gpu 8mo agoIt is pointless, because in Linux all you get is a virtual address. Physical backing is only allocated on first use. In other words, the first time you access a "freshly allocated" non-null pointer you may get a page fault due to insufficient physical memory.
- 3836293648 8mo agoBy default, yes. You can configure it to not overcommit
- david-gpu 7mo agoRight, but as a programmer you rarely have control over that. And even if you do, you often can't handle out of memory errors gracefully. Thus, for a typical situation it is reasonable to log the error and bail out, rather than adding extra custom error handling around every single memory allocation, which ends up being code that is never tested.
- clarabennett26 8mo ago[dead]
- norir 8mo agoI have a personal aversion to defer as a language feature. Some of this is aesthetic. I prefer code to be linear, which is to say that instructions appear in the order that they are evaluated. Further, the presence of defer almost always implies that there are resources that can leak silently. I also dislike RAII because it often makes it difficult to reason about when destructors are run and also admits accidental leaks just like defer does. Instead what I would want is essentially a linear type system in the compiler that allows one to annotate data structures that require cleanup and errors if any possible branches fail to execute the cleanup. This has the benefit of making cleanup explicit while also guaranteeing that it happens.
- simonask 8mo agoIf you dislike things happening out of lexical order, I expect must already dislike C because of one of its many notorious footguns, which is that the evaluation order of function arguments is implementation-defined. About RAII, I think your viewpoint is quite baffling. Destructors are run at one extremely well-defined point in the code: `}`. That's not hard to reason about at all. Especially not compared to often spaghetti-like cleanup tails. If you're lucky, the team does not have a policy against `goto`.
- ameixaseca 8mo ago> I have a personal aversion to defer as a language feature. Indeed, `defer` as a language feature is an anti-pattern. It does not allow the abstraction of initialization/de-initialization routines and encapsulating their execution within the resources, transferring the responsibility to manually perform the release or de-initialization to the users of the resources - for each use of the resource. > I also dislike RAII because it often makes it difficult to reason about when destructors are run [..] RAII is a way to abstract initialization, it says nothing about where a resource is initialized. When combined with stack allocation, now you have something that gives you precise points of construction/destruction. The same can be said about heap allocation in some sense, though this tends to be more manual and could also involve some dynamic component (ie, a tracing collector). > [..] and also admits accidental leaks just like defer does. RAII is not memory management, it's an initialization discipline. > [..] what I would want is essentially a linear type system in the compiler that allows one to annotate data structures that require cleanup and errors if any possible branches fail to execute the cleanup. This has the benefit of making cleanup explicit while also guaranteeing that it happens. Why would you want to replicate the same cleanup procedure for a certain resource throughout the code-base, instead of abstracting it in the resource itself? Abstraction and explicitness can co-exist. One does not rule out the other.