247 comments

[ 5.7 ms ] story [ 328 ms ] thread
Despite being convenient,I have the feeling that it might be a bad idea.

One of the key interest of C compared to more recent languages is that everything is explicit. With a finger you can follow the code as it runs and know exactly what is going on and when exactly.

At the opposite, there is c++ that does a lot of things automagically. And it is often hard to understand why you suddenly get a segfault out of blue, just because a destructor was randomly called at the wrong time.

>One of the key interest of C compared to more recent languages is that everything is explicit.

There's nothing implicit about defer.

Deferred actions are not explicit at the exit points.

That's quite implicit. You can no longer reason about a local piece of code; you now have to know its lexical nesting up to top level to see if it's inside a guard block that might trigger hidden behavior.

That's still local at the scope level though, which is quite acceptable.

Plus, to handle the free or the leak if you had forgotten to free a resource at the exit point would also require to "know its lexical nesting up to top level".

Between goto and longjump and co, C has much worse non-local behavior than defer.

Goto isn't nonlocal. You know you'll only every jump from them, and that you'll only ever jump to the specified label.
The specified label is what makes goto no-local. You need to check the whole codebase to find out where you'll land.

By your definition only [1] "come from" would be non-local.

[1] https://en.wikipedia.org/wiki/COMEFROM

> the whole codebase

You can't goto out of a function, and you know there's exactly one such label inside it. If goto isn't local, then neither are function calls, since the function could be defined anywhere in the codebase.

FWIW, Normally the term "non local goto" is reserved for control transfer beyond the current activation frame. So C goto is strictly local, bit longjmp would be non-local.
I wondered about this. Say I have a block, at the end of which I free a bunch of memory. I also have a bunch of other exit points within the block (mostly for catastrophic errors, say). Would a deferred free only apply to the outer scope? Because if so, I really don't see the point at all.
yes, you always run defer. I don't see it in this proposal, but Most languages/frameworks that implement a defer also implement a "error defer" which only gets triggered on some sort of labeled early exit scenario, and some implement "success defer": (i believe this is scopeguard: https://www.youtube.com/watch?v=WjTrfoiB0MQ)
it's control flow. What you are saying is like saying "while loops are implicit: you can no longer reason about code; you now must think about the state of the check boolean to see if it will trigger the hidden behaviour of going back to the top of the loop because there's no explicit "go back to the top of the block" statement.
With a while loop, to know what happens at the bottom of the block, you only need to check the top of the block. With defer, to know what happens at the bottom of the block, you need to check the entire contents of the block.
> With a while loop, to know what happens at the bottom of the block, you only need to check the top of the block.

Anyway, what could possibly happen at the end of a while loop, besides going back to the begining for the test?

Besides, if you have a bug in your code, you will have to look at the whole block anyway.

What? Any statement inside a while loop could mutate the loop variable and then all bets are off.

You have to check every line of a while loop to know what’s going on. Heck, another thread could hold a pointer aliasing the loop variable and then mutate it, causing the loop to terminate for no obvious reason.

My point is that you know at the end of the while loop, it's going to test the loop variable you wrote at the beginning, no matter what's in the loop body. The fact that other things could change the variable isn't relevant. With defer, you don't know what will happen at the end of a block without looking through the whole thing.
Why does it matter so much what code runs at the end of a block?
It's as much hidden behaviour as destructors being called magically on an object that goes out of scope. In practice it doesn't hinder understandability as much as you think.
> destructors being called magically on an object that goes out of scope

Which C doesn't have.

> Deferred actions are not explicit at the exit points.

They are no less explicit than the actions performed at the end of iterations of for or while loops. In general, for C, understanding the behavior of code requires understanding “is it in a block, and if so what kind of block”; guard blocks would be not generally different.

Every defer statement is ultimately pushing a lambda into some stack that is executed when the defer block is exited. With an exception of setjmp/longjmp, I can't think of an existing C construct with similar run-time complexity.
That's indeed tricky: if there's a for loop inside the guarded block which generates many defers, a stack would have to be created to handle them somehow.
The defer would just run at the end of each iteration.
(comment deleted)
Is it actually a lambda, or is it just essentially appending statements to the end of the scope?
that depends for instance you can safely call free on NULL so you could just append.

However, for some things you can only free/return resources if you successfully created that resource. At which point you would need to use something like a stack.

I think the broader and more accurate point is that defer adds cognitive load to reasoning about the order of execution. It's true that defer (at least everywhere I've seen it executed) is totally explicit and deterministic, but in the case of multiple defers in the same block it can take some thought to reason through exactly what happens when.
Destructors are not called randomly.
"Smart pointers go brr"

It's not really random, but it's quite hard to follow.

That's really an argument against smart (to be precise, shared) pointers, not against destructors.
If it's too cold for you, it's too cold for your smart pointers. Bring your smart pointers inside during the winter months.
It shouldn't be. std::unique_ptr is very clear.

(std::shared_ptr should be used only when absolutely required and should be kept under close watch the whole time.)

searches codebase I'm working on... finds thousands and thousands of std::shared_ptr (many involve a typedef, so there are even more...) eep!

Should I be alarmed?

Yes. On reflection, or on ramping up new developers, I think you’ll find that it is unnecessarily difficult to reason about object lifetimes.

There’s a good chance that most of the shared_ptr’s can be replaced by unique_ptr, which has less runtime overhead. More importantly, it documents the intent of the programmer regarding ownership semantics, and the timespan during which the object should be remain valid.

If that codebase is older than C++0x, many of these std::shared_ptr could be taking the place of std::unique_ptr (which didn't exist back then; std::auto_ptr was a footgun). If you're in the mood for a refactoring, you could take a look at each one and see whether then can be replaced by std::unique_ptr, or whether they really have shared ownership semantics (which is what std::shared_ptr should be used for).
Neither std::unique_ptr nor std::shared_ptr existed prior to c++11. Perhaps you're thinking of boost::shared_ptr or boost::scoped_ptr, both of which have existed in one form or another for somewhere around 20 years.
Not necessarily. The concern about when the shared_ptr'd value runs its destructor only matters if the value's destructor is something you care about.

If you have a situation where a value needs to be shared between multiple other values, but also the value is trivial enough that it doesn't matter when it's destroyed or what it does when it's destroyed, but also the value is not so trivial that you can copy it instead of using a refcounting pointer, then std::shared_ptr is fine.

"Fine" is probably pushing it, but "not the biggest problem you are likely to have" is probably true. But, like, it's easy to accidentally get yourself into situations where somebody forgot to use std::weak_ptr somewhere and you start leaking memory.

Unless you know you must share objects with multiple potential owners, though, std::unique_ptr is a lot wiser.

Yes, when I wrote:

>but also the value is trivial enough that it doesn't matter when it's destroyed or what it does when it's destroyed"

that precludes the value from having other refcounting pointers that could then create cycles and leak.

As replied by someone else, by 'random' I did not mean really 'randomly', because the machine is determinist and follow a logic.

But more that there is so much magic and abstractions that it is very hard for a dev to have a clear view of what is going on and what to expect. He has to 'guess' instead of just read the code. For that, I guess that it is similar to the current question of accountability of decisions made with deep learning algorithms.

As an example of my point, I would refer to the 'garbage collection' issues of a language like 'java'. GC will happen at a logically defined point like 'dirty mem > 100m' but from the developer point of view, his logic could suddenly lag unexpectedly in a middle of a simple operation because the GC was triggered by internal magic. It is very hard for a dev to be able to determine the memory usage at different points of the code and so have a certitude of when this operation could happen.

> As an example of my point, I would refer to the 'garbage collection' issues of a language like 'java'

Two languages could hardly be less alike than Java and C++ - the latter is not garbage collected and where destructors are called is completely predictable.

You’re missing the point. The two are alike in that something significant is happening that you as the programmer did not explicitly tell it to do. The specific mechanism really isn’t an important distinction here.
If you write a destructor then you know exactly under what circumstances in C++ code it will be used (this is one of the major features of the language). This specific mechanism is extremely important.
Nobody is claiming that it isn’t well-defined nor unimportant.
There is no guarantee when a Java finalizer will run, whereas a C++ destructor is guaranteed to run based on the code that you write. That's a big difference which has practical implications, using something with non-guaranteed behaviour as an example of why something with guaranteed behaviour is hard to predict seems like a misunderstanding.
I believe the situation is the same in C++, that is, if you have a shared_ptr cycle, the destructor will never be called, no?

It is more deterministic in C++, but you can still call exit(). (which is even more straightforward than the refcount cycle)

> I believe the situation is the same in C++,

in the specific case of shared_ptr, which are a small part of codebases, if they are even used - for instance an immense amount of C++ GUI programs use Qt which doesn't use shared_ptr-like ownership semantics but instead a tree-of-objects model which does not have this issue. In contrast in Java / C# any object that has a reference to another is at risk.

Sure. That’s why I said “more deterministic.” But even a rare event disproves the guarantee.
Sorry, I meant that the behaviour is guaranteed, not that the destructor is guaranteed to be called. So in the Rc cycle example the guarantee is that the destructor won’t be called.
Of course it has practical implications and is harder to predict, but neither of those are what this conversation is about.
> something significant is happening that you as the programmer did not explicitly tell it to do.

when I put some object on automatic storage in C++ I do so because I explicitely want it to go away when its scope is left (either by reaching } or through an exception)

guard/defer is semantically and syntactically as explicit (or implicit) as C's automatic storage class (https://en.cppreference.com/w/c/language/storage_duration), which is the default storage class.
Only if you include dynamically sized arrays, which are deprecated because of their problematic allocation behaviour.
I don't believe that VLAs are actually deprecated as such, but have been moved to an "optional" feature for implementation in C11. There's a feature macro to check, __STDC_NO_VLA__, but I don't think the feature is actually disappearing from the standard any time soon.
I wonder what is a good use for VLAs btw. I used it for a string padding function to create the padding with a simple loop before a call to strjoin. Is that a bad idea or a legitimate use case?
Don't use VLAs for anything that actually requires an allocation. However, VLAs are quite useful when you have a buffer and want to access it like an array.
Despite being convenient,I have the feeling that it might be a bad idea.

What is "I am thinking of using C?"

Depending on your platform, it may be your only reasonable choice.
I agree completely. For the most part it seems if a person has to think long and hard about it, they probably should not use C and if it is the obvious choice that’s probably because C is ordinary in the context. These days there are probably fewer edge cases than in the past.
>One of the key interest of C compared to more recent languages is that everything is explicit.

With macros this is not exactly true. I gesture towards the GObject system for an extremely complex situation in big important production software.

I agree. I have tried to come up with some reasonable way to constrain macros, but its really hard!
The way to do it is, survey your macro use to identify common patterns, specify them formally, and then write a computer program that allows those patterns specifically but no others. In other words, design a higher-level language. ;)
So, what’s the alternative? Not freeing resources is a very common error; it would be nice if the compiler could help detecting it.

A possible explicit solution I can think of is to introduce a new function attribute that gives the name of their ‘cleanup’ function (so that the compiler would know fopen needs a fclose, for example) and a compiler that uses these attributes to issue a warning if a function has a path that calls a function and doesn’t either call its cleanup function or returns its result.

I don’t know whether that would cover all bases, though.

I view it as another element of structured programming. It can be expressed as a series of "goto" statements, same as for, while, do, switch. It's just one they didn't think of back when C was being designed. If it was in from the beginning, nobody would think it strange.

That said, it's still debatable if it's useful, given that you can achieve the same thing with the

    struct some_resource resource;
    do {
        resource = allocate(...);
        if (!resource) break;
    } while(0);
    if (resource) dispose(resource);
I can see it as a good thing because you have the dispose statement next to the allocate statement, which makes the logic easier to follow, but the implementation may have caveats which make it actually harder to reason about, e.g. see the other thread about capture value vs capture reference - C will most probably need to capture by reference, which means that modifying "resource" later on changes the meaning of the deferred statement.
I've heard this comment more than once. What we are trying to accomplish is to collocate the resource acquisition code with the acquisition release code. This does mean that the release code is removed when where the release occurs (for example, at every location a function returns.

However, this is not without precedence in C. For example, just look at the for loop:

for (clause1; expression2; expression3) statement

expression3 is executed after statement.

I would argue that the C for loop is a rather awkward construct. It's found its way in many languages, so I think most people are used to its idiosyncrasy but it's not great if you try to take a fresh look at it.

I think the best defense of this syntax is that it makes writing basic iteration a bit nicer without having to add boilerplate (the iconic `for (i = 0; i < n; i++)`) but then I would argue that the real problem is that C is severely lacking in the iteration department and this is a rather obvious hack (that languages like Javascript felt the need to copy wholesale, for some insane reason).

The nonlocality can be eliminated by replacing the `guard {}` block with a `resolve;` statement to be placed at the end of a block containing multiple `defer`s. This also reduces nesting and solves the question of where the `defer` is executed and makes it easy to grep to the location where the `defer` statement will be executed. Of course, it would be a syntax error to use `defer` without a following `resolve;`.
"Explicit" in language design is catch-all term for several different things: https://boats.gitlab.io/blog/post/2017-12-27-things-explicit... (I highly recommend this article.) Defer is still manual and local, just less noisy and burdensome.

Just like every C feature: if you know how it's implemented (and optimized), you will know what is going to happen. All big C compilers already support stack frames and unwinding, so it's not even entirely novel functionality.

While you may object it's not entirely obvious how unwinding is going to be implemented, OTOH in functions with complex control flow it can be easier to understand which `defer`s are going to be run, as opposed to following nested `else` statements, or reasoning about the program state from all `goto cleanup` locations.

> One of the key interest of C compared to more recent languages is that everything is explicit. With a finger you can follow the code as it runs and know exactly what is going on and when exactly.

I would suggest functions like atexit and pthread_cleanup_push as well established counterexamples. Granted this is not a perfect comparison because these are implementable without extending the c language; however, I think they have the same general idea of "defering" cleanup. I think the proposed defer functionality is actually more readable because the defer command will be written much closer to where it will be exited. Compare this to atexit() which may be put anywhere.

One of the key interest of C compared to more recent languages is that everything is explicit. With a finger you can follow the code as it runs and know exactly what is going on and when exactly.

This has not been true in many years. C appears to be such a language, but optimizing compilers have learned how to find and optimize undefined behavior in code that most C programmers don't realize is unsafe. As a result there can be a considerable gap between the code as clearly intended, and the code that will be generated.

See https://blog.regehr.org/archives/213 for more on this. Including real world examples of things like validation checks being elided by the compiler, leading to real-world vulnerabilities in programs which clearly have checks to avoid exactly those vulnerabilities.

Despite being convenient, I have the feeling that it might be a bad idea.

Probably. It's one of Go's lesser ideas.

C already has a "defer" mechanism in "exit", to close out files and such. Of course, the final I/O status gets lost.

> One of the key interest of C compared to more recent languages is that everything is explicit. With a finger you can follow the code as it runs and know exactly what is going on and when exactly.

I don't agree! There's a lot of behavior that's implicit and you essentially need to internalize the C standard/compiler behavior to follow. Weak typing, for instance; defaults for memory access/fencing, handling faults, runtime semantics with respect to initialization of the process, cleanup via atexit, and signal handling.

Memorable, sure, but hardly explicit.

I’ve always considered “defer” an inelegant kludge in comparison to RAII for automatic cleanup of resources. It’s surprising that the C standards committee is considering adding that to the language. Then again, nearly none of the syntactic C features post-C89 have been very compelling or widely adopted.
Defer is more explicit than RAII, so a better fit for the very explicit C languag
Defer is more explicit than RAII, so a better fit for the very explicit C language
It’s even more explicit to just call your cleanup routines manually, so by that logic “defer” is inferior to existing methods of handling cleanup in C
subroutine calls requires implicit allocation and deallocation of stack frames. Real Programmers™ use goto and manage the stack by hand.
> subroutine calls requires implicit allocation and deallocation of stack frames. Real Programmers™ use goto and manage the stack by hand.

Could u point to an example of programmers doing this ?

defer is great for many use cases, available in Go, Swift and other languages, and nothing like a kludge.

If anything RAII is unfit for C (which doesn't have classes), and in itself, a kludge (it's an idiom, not a language feature).

What if you forget to call “defer”? Then your code is incorrect. That’s kludgy in comparison to RAII where it’s impossible to forget to call a destructor.
The whole point is that it's much easier to NOT forget defer (which goes right after the resource acquisition line), than to forget to free the resource (which happens much further down the function).

So it's immediately better compared to the current C situation.

As for compared to RAII? Well, thats one failure mode for defer (forgetting it), whereas there are dozens of ways to mess RAII...

I made no comparison between “defer” and the current C situation.

In comparison to RAII, “defer” as a language feature is a kludge. Mainly because RAII completely solves the problem of correct resource cleanup for API users while “defer” does not.

RAII is an idiom, not a language feature. Destructors might be the language feature, but they have their own issues and caveats.

So I don't see the comparison...

I think it's quite reasonable to compare defer and destructors, in that case.

Destructors probably cannot be usefully imported into C while preserving the simplicity of the language: then you'll want at least unique_ptr to manage your memory with destructors, then probably shared_ptr, and some ownership semantics as you pass those around, et cetera. In that sense, defer strikes a balance between simplicity and usefulness. However, there is still a comparison.

RAII is a delight when it comes to local heap memory. However, if you want to pair fclose with a fopen call then you need to wrap fopen in a type and make the destructor call fclose. This is totally fine and I like it a lot in C++ and Rust, but it doesn't gel with existing C apis. Therefore I think defer is a good choice for C regardless of what I might choose were I to be implementing a language from scratch.
Non-obvious need to call defer was what made me stop trying out Go. When I was making HTTP requests there was no Close() method on the response so it seemed like it wasn't needed. But later I found out that there is a Close() method on the response's Body member that needs to be called (only one code sample in the docs even showed that).

RAII is the best for ensuring that things get cleaned up even if it does lead to more boilerplate to add the pattern to things. But even .NET's IDisposable feels better than defer. It's established practice to implement it if your class contains something that must be disposed. And it's easy for tools to check if you haven't disposed of something that implements IDisposable.

All that said, I find defer to be better than nothing. In C, the best I can do for cleanup is having all the cleanup functions called at the end with labels at the various points I need to jump to.

I would strongly disagree on RAII being a kludge in C++. The language feature that enables it is deterministic destructors, whose primary purpose is to ensure that allocated resources are deallocated. RAII is one particular use of destructors. The primary goal is to have the object be the thing that owns a resource, not the calling scope.

In terms of usability, destructors allow for resource management that is far easier at the call site than any other language. "with" blocks such as in python require the call site to be modified to include the explicit time of destruction, whereas C++ gets that for free from its existing scoping rules. "try/finally" such as in Java also requires modifying the call site, but at least has reasonable default behavior if accidentally omitted. "defer" feels like it has the worst of both worlds. It requires the call site to be modified when a resource is owned, and also requires the caller to know what the corresponding cleanup function is for any allocation.

I do feel that RAII is a little bit ruined by the fact that you cannot declare anonymous object instances.

For example, I cannot write:

    lock (mutex);
Expecting to declare an anonymous lock with the mutex passed in, as this gets parsed as a declaration of a lock called mutex (hopefully lock doesn’t have a default constrictor and I at least get an error). Instead I have to bake my lock:

    lock l(mutex);
Often I have objects that only exist for RAII and it’s a little bit annoying to have to give them dummy names.
IIRC, the problem with C++ is that you can declare anonymous object instances... With surprising results (the object is destroyed at the semicolon, instead of being destroyed at the end of the block).
It is quite dangerous. IIRC some compilers do catch the issue and warn about it.
Sure, you can, but its still not the behaviour you'd expect and the problem in my example is that "if it looks like a declaration, it is", so "foo bar;" and "foo (bar);" can be the same thing.
it is a bit annoying yes. There are some proposals to have a anonymous reusable placeholders like '_', but they haven't gone anywhere yet.

You can do

    lock(mutex), some-lock-protected-expression; 
but it is a bit too cute and limited.

On the other hand, being able to name the RAII object is often very useful for example if you need to dismiss them early, which happens often in transactional code (unlocking a mutex before the end of scope is a relatively common occurrence).

Also specifically for mutexes, a nice pattern is to bind object and mutex together to guarantee that the object is always used with the lock:

   sync<my_object> foo;

   {
     auto l = foo.lock(); // starts the critical section

     l->do_something()
     l->do_something_else();
   } // it ends here

   // or if you only need to hold the mutex for a single call:

   foo->do_something(); // critical section lives only for the full expression
> On the other hand, being able to name the RAII object is often very useful

I didn't say it wasn't. Being able is perfectly fine, being forced is not. As I mentioned in another comment, the problem isn't the extra typing, the problem is that it requires us, the programmer, to remember to name it even when we don't feel we need and to remember that "foo (bar)" isn't calling the constructor of foo and passing in bar, its calling the default constructor and declaring bar. That's too many gotchas and rather error prone!

I believe there was a cppcon talk where the speaker said that it was a very common bug in Facebook, despite that they have linter rules to catch this case. Its also a rather insidious bug, because the code will run seemingly normally, just... it never actually locks anything. A rather hard problem to debug too.

yes, I think I hit it at least once and flagged I don't know how many times in code reviews.
> Its also a rather insidious bug, because the code will run seemingly normally, just... it never actually locks anything.

Actually, I think it's worse than that; IIUC, it will block until the mutex is unlocked, then run. Under light load, it will get away with this, but if the load is heavy enough, it will either guarantee that every process waiting for the lock runs at once, trampling each other's work, or almost-serialize them, making the race condition even more intermittent than if they didn't lock at all. Which failure mode you hit depends on how your scheduler works.

Workaround:

    #define LOCK(mutex) lock lock##__LINE__(mutex)
Or even C compatible scoped locks:

    #define SCOPED_LOCK(mutex) \
        for (int i##__LINE__ = lock_mutex(mutex), 1; \
                 i##__LINE__ --; \
                 unlock_mutex(mutex))
Use like

    SCOPED_LOCK(foo->mutex) {
        do_stuff();
    }
Of course there are workarounds, but that's not the point. First of all, your solution requires macros and the problems that come with that, but also, you now require programmers to remember to use your macro instead of just declaring the instance.

The problem I'm describing is less about the effort of adding a variable name -- that's really not a big deal -- its that its super error prone to remember and these bugs slip through all the time. Requiring a variable name (either explicitly or through a macro like yours) is error prone.

Sure you can, it just requires a little bit more syntax and following FP patters by making use of lambdas.

With lock() being a function taking a lambda.

    lock([]{
       // code locked with anonymous mutex
    });
Also defer runs even if your code panic which is great.
... which is exactly how C++ RAII behaves when there is an exception ?
IIRC in C++ this is only so if there is a try/catch underneath.
Exactly. I always explain this but people tend to ignore and not see how error prone defer is. It is a shame that a kludge (ugly hack actually) like this receives so much praise.
RAII is a special case of defer, due to how it abuses constructors and destructors to hook into the points of entering and exiting scope. You could write a RAII implementation in terms of defer but not the other way around.

RAII doesn't help with a generic pair of init and deinit functions, such as malloc() and free() for example, unless you wrap your mallocs into "memory objects" or something. You can't do anything with RAII unless you wrap your stuff into objects which just forces the OO crap on everything regardless of whether it's a good fit for the OO paradigm.

But even in RAII, classes are merely just a way to bind init and deinit together. As a result, you can't "forget" to free the resource. (Unless you use the new operator...) But this is merely an interface issue: defer could be of the form

    void *block = defer malloc(SZ) with free(block);
or something similar that requires the init and deinit calls to be part of the defer expression itself.

Nevertheless, a defer is clearly a useful language feature that C actually lacks. Currently C doesn't offer the code any attaching points to the lexical scope of the program. You can fake it with a special for(;;) statement but it's a kludge and even by wrapping it into a macro it's very hard to make it a generic solution.

> RAII doesn't help with a generic pair of init and deinit functions, such as malloc() and free() for example,

people have been writing RAII scope_exit classes for at least 20 years to do exactly this.

> You can't do anything with RAII unless you wrap your stuff into objects which just forces the OO crap on everything regardless of whether it's a good fit for the OO paradigm.

wrapping something in an object does not make it OO. For example there is nothing object oriented about std::unique_ptr.

Well, you can only wrap objects in one, although you can "wrap" a primitive by writing lambdas for this purpose.
It's the other way around. Defer is only capable of dealing with the surface level of RAII: creating and destroying something which only exists on the stack for a defined period. RAII is much more, it deals with the full lifetime of the object, including objects with dynamically defined lifetimes, and the lifetime of objects which are members of other objects. And you can implement defer with RAII very easily, with zero overhead, in C++. (there's a similar misconception which holds that features in garbage collected languages like try-with-resources, with, or using statements are a complete replacement for RAII, when they have the same problems.)
> Then again, nearly none of the syntactic C features post-C89 have been very compelling or widely adopted.

That's Microsoft's fault; it's 2020 already, and unless things changed since I last looked, their compiler still doesn't have full support for the C99 standard.

Even then, some of the C99 syntactic features have been somewhat widely adopted (at least when not compiling for Windows); off the top of my head, we have "//" comments, declarations in the middle of a block, and designated initializers.

They fully support C11 and C17 now.

Everything that was moved into optional in C11, like VLAs, is not planned to ever be supported.

“Somewhat widely adopted” is not “widely adopted” :)

I see people tend to write C89+designated initializers but this is a code smell IMO. Initial data should always be 0, especially data that lives in BSS/data section.

RAII can be an anti-pattern for certain high performance applications. Yes a lot of times you want the assurance you're always dealing with properly initialized memory, but if you're really optimizing for memory performance (one of the jobs C is for) then a lot of times initializing and deinitializing small blocks of memory is exactly the opposite of what you want to do.
that's what arenas are for
Jumping back in C always means trouble.
Defer is a jump forward (at error points, to scope end) but it's declared in-line.
Isn't this already available as a GCC/LLVM extension? I know several codebases that use it in that form.

Good to see it standardized of course...

I don't think defer is really "available", but it can be implemented as a macro and the use of some non-standard extensions.
Yes, as a macro and extensions (like __cleanup__) combo.
GCC has a similar extension for defering to end of scope, which is basically most of use cases(cleanup function is usually free or some destructor/sanity check function). https://echorand.me/site/notes/articles/c_cleanup/cleanup_at...
It’s annoying that the C committee does not standardize existing widely-used extensions that are supported by multiple compiler implementations.
This is amazing, thanks! It's a bit different though - it executes a cleanup function when the variable goes out of scope, rather than a statement at the end of the enclosing guard block. It might be better though - it avoids some footguns that would be possible with a defer implementation.
Clang has this too. Libraries like Glib and software like Systemd use this mechanism. Standardizing it would be nice.
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

>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...

Sure. 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.
>Sure. But defer just changes the problem from forgetting to write free to forgetting to write defer.

Yes, which is a much better formulation.

I 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?

> 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.

I 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.)

> 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`.

You have accused me of arguing in bad faith twice in one post. This is insulting, not to mention against the HN rules ("assume good faith").

Clearly we disagree on whether a syntax change is minor. But first let me repeat the point I made that you ignored in between your accusations: it really is just syntax. Of the four examples in the second part of your post, if we assume defer is implemented like attribute cleanup and fix up the compile errors, your first and fourth example compile to the identical assembly code:

https://godbolt.org/z/n14z5q

https://godbolt.org/z/Ksq1rj

I would argue that the best solution is one you didn't present: move the "business logic" into a separate function, one that takes the necessary resources as arguments. This way you're no longer mixing up resource acquisition error handling with business logic, and the function that acquires the resources can use the nested if statement style (or any other style) with no downsides. No surprises here, it again compiles to the identical assembly code:

https://godbolt.org/z/b375E9

In my opinion the nested if style is better than using defer because it's completely linear with no backward jumps. But even if you disagree you can hardly complain about cleanup code being far from init code because the whole resource handling function is less than 20 lines of code regardless of what cleanup style you chose. It doesn't matter, which is why I argue that it's a minor syntax change not worthy of addition to C.

> it really is just syntax

It's really not. When the impact of "syntax" are non-local like that, it's more than syntax. A compiler would handle this beyond the parsing stage. At the very least, it would seriously massage the AST to remove `defer` from it.

> if we assume defer is implemented like attribute cleanup and fix up the compile errors, your first and fourth example compile to the identical assembly code:

This is to be expected: they ultimately do the same thing, and optimisers are known to do significant, non-local transformations to the code.

> move the "business logic" into a separate function, one that takes the necessary resources as arguments.

So now I have a function with (likely) too many arguments, that's used only once, and my eyes have to jump around to get to it (or I have to reach for the F2 key). The pyramid may be more visible, but that's a meagre advantage.

> In my opinion the nested if style is better than using defer because it's completely linear with no backward jumps.

Not even a criterion in my book. I suspect you're having an overly operational mindset. A mindset I suspect has held the whole field back a couple decades. Don't think of it like a backward jump. It's meant to be viewed as deferred execution, triggered by scope exit.

> you can hardly complain about cleanup code being far from init code because the whole resource handling function is less than 20 lines of code

That was an example, dummy. In real code, I'd have more than 3 things to initialise, and their initialisation might not be as trivial (or as repetitive) as what I've shown here. That's when I really want to read the code from top to bottom, with concerns packed together. Defer/cleanup lets me do that. The other solutions, less so.

Of course. There should never be more than one way to do something in c, even if it makes things easier or less error prone. This is why the -> operator would never be included in c. You can already use (*x).y, it would be SILLY to introduce a whole operator which isn't orthogonal to all existing features of the language.
I never said C's language features are orthogonal. I said they should be. C is far from a perfect language, as your example shows.

Look at it from the opposite direction: if x->y didn't exist today, and the billions of lines of existing C code all used (*x).y, would you support a proposal to add a new x->y operator to the language? I doubt it.

I would absolutely support something to replace ((((a).b).c).d).e with a->b->c->d->e, regardless of how much code had been written without that feature.

Do you not like the array subscript operator, either, since a[b] can be *(a+b)? How about a && b, you can replace that with (!!a) & (!!b), with an extra 'if' if you need the short circuiting.

>I think this example demonstrates the opposite of what you intended. Defer doesn't give you anything that you can't already do.

Well, that's the whole point of syntactic sugar.

Not that it gives you something you can't already do, but that it gives you a succint and better way to do it.

>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 beg to differ, based on your definition of "extremely painful". Many kinds of syntactic sugar are welcome, even when the previous native solution wasn't "extremely painful" but e.g. just tedious or error prone.

The 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#...

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.

It's not so bad if you obsess over valgrind warnings.
That 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.
Maybe, 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.

It 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.
That would be the easiest way. Definitely a disadvantage
Please just standardize the already existing attribute((cleanup)) mechanism which is already being used by lots of Linux software. This new mechanism is incompatible while bringing no benefits.
Defer has the benefit of being dynamic vs the scope tied cleanup.
Not sure I'd call that a benefit, honestly.
I think it depends what problem you're trying to solve. If you're just trying to kill kernel-style GOTO cleanup pyramids, then having it tied to scope with no unwinding or anything dynamic is perfectly adequate.
I would tend to agree with you. Unfortunately this proposal is for much more than just block scope cleanup.

This proposal contains a specification for complete stack unwinding in C. It doesn't just specify defer, but also panic and recover, which jumps between functions and cleans up guard blocks across stack frames. It's essentially exceptions for C. This is frankly horrifying and I can't believe the C standards committee is entertaining this.

They claim that this is a separable feature from defer, so maybe they intend for defer to be mandatory and panic/recover to be optional. But much of the design of defer is to support their panic/recover mechanism. This makes it much more complicated than attribute((cleanup)). If they want defer to be taken seriously, they should move all of the panic/recover stuff to a separate proposal. I suspect if they did this, a lot of the design recommendations they've made for defer wouldn't make sense on their own.

Doing other things in the proposal is no argument against having an attribute((cleanup)) compatible syntax for the part that overlaps.

Also, the defer syntax they're proposing seems nicer in terms of syntactic sugar (no need for a stub function), but at the same time the semantics seem ... weird. Having it tied to a variable makes more sense, and sidesteps a whole bunch of weird situations (like loops with defer statements).

In the contrary, I find it really weird to bind all cleanup code to some kind of object with constructors and destructors. People seem to have gotten so used to this ... I think that for C it is much more natural to have a control structure in the language for this.
“I can't believe the C standards committee is entertaining this.”

Hey, standards committee’s gotta eat.

'defer'? I occasionaly use it in Swift to clean up resources; s’okay there, I guess, though I’m not convinced it’s better than Python’s 'with' block. But in C?

One of C’s few distinguishing strengths is that the language is relatively† small and stable, and well understood. For that kind of cleanup there is already 'goto', which again is small, stable, and well understood. I just used it for that the other day: it works, it’s fine; I’m a grown-up.

Yeah, sure, 'defer' is “safer” and “more elegant”… did we mention this is C? That ship sailed fifty years ago. Don’t try to make C into something it’s not: that’s C++’s job so go fill your boots there instead.

I just posted Tony Hoare’s excoriation of ALGOL68 the other day, but clearly it’s needed again:

http://zoo.cs.yale.edu/classes/cs422/2011/bib/hoare81emperor...

Simplest solution: track down the ruddy C standards committee and beat them in the head with a leather-bound copy of Zawinski's Law (wrapped around a large gold brick), till either they’re dead or they leave C be. It does what it was designed to do, and that along is reason enough not to dick with it just because they’re bored and struggling to justify their continued existence.

The only thing C needs to do is keep on working. That will only get harder the more crap they pile on top. A good artist knows when to stop.

Which brings us to…

“panic/recover”

K&R give us strength! Tell these frustrated wannabe language designers to go make their own damn language, instead of screwing up someone else’s!

Okay, now I’m done. And get off my lawn!

More features being added to a newer version of the standard doesn't prevent you from staying with the old version. Your trusty old C89 compiler will still continue to work.

Speaking from experience, GCC/Clang extensions are pretty useful when you can afford to use them and it would be nice to have some of that stuff standardized because a lot of code I see in the wild is already using them. The time for warning about C becoming a mess of incompatible extensions has already passed unfortunately, and the only group that can solve this is a standards committee.

I couldn't agree more and created an account just to second your thoughts.

This is exactly why everyone hates using modern C++. Their standards committee has run amok with every fancy feature that every new language comes out with in the past two decades. And what has that wrought? A huge language that no one fully understands and is hard to read unless you happen to be the guy that wrote it and time since writing < two weeks.

C is simplistically beautiful. I can write it and understand exactly what the assembly will look like on the backend of the compiler (with the exception of nasty macros). Don't mess with that. If it doesn't feel like it belongs, that's probably because it doesn't! Know when to stop.

> This is exactly why everyone hates using modern C++.

speak for youreself ?

> Their standards committee has run amok with every fancy feature that every new language comes out with in the past two decades. And what has that wrought?

given the amount of languages that reimplement C++ features (and sometimes have to be dragged by the feet to get them, e.g. generics and interface methods in java), I'd say that people who don't understand c++ are doomed to reinvent it :)

This seems really strange to me. The C committee recently seemed pretty opposed [1] to adding fat pointers [edit: originally wrote bounds checking but that was a thinko] as eg WalterBright has called for. [2] Addressing that seems like it'd have a lot more benefit for a much smaller change to the language. I don't really agree with you on "the only thing C needs to do is keep on working" but if they're not willing to entertain that proposal, why on earth would they be talking about throwing in exceptions?

[1] https://news.ycombinator.com/item?id=22866288

[2] https://news.ycombinator.com/item?id=24454369

>It's essentially exceptions for C. This is frankly horrifying and I can't believe the C standards committee is entertaining this.

I can because C already includes setjmp/longjmp as an exception mechanism that gets used in plenty of real code. The problem here is that those don't unwind the stack so they would break and cause memory leaks when using defer statements.

If you're already using setjmp/longjmp I would suspect you are also already using an arena or avoiding heap allocations, so I don't know if it would specifically be a problem for memory leaks with defer.
This is... wild. I had no idea attrribute((cleanup)) was a thing in CNU C. Did the C folks finally discover the concept of a destructor? What's the point of resisting C++ so adamantly if they're going to introduce the same features with an awful nonstandard syntax in an ad-hoc manner anyway?
The attribute(cleanup) is just exposing to C the C++ destructor functionality that is already implemented in the compiler. That's why it's using the awkward nonstandard syntax.
C++ is a lot more than defer, obviously. There’s nothing wrong with extending the language for common use cases.
ISO has to care about much more platforms than just Linux.
Which is why we want it to be standardized. It's already widely used on Linux and BSDs, the big compilers already implement the underlying functionality (because it's needed by C++), GCC and LLVM implement the exact same extension in an identical way, it does everything that people need, so let's make it work officially.
Still there is a life beyond UNIX clones and gcc/clang.

The C compiler vendor for some embedded CPU with homegrown C compiler for their in-house OS also has a seat at ISO table.

While people here might not care, ISO does.

I agree. So go with the well-established widely used change, not the baroque and ill-defined proposal in the original post.
The proposals generally exist so that compiler vendors can comment on them and try proof-of-concept implementations if they're interested.
Indeed, and the fact that this is being proposed instead of adopting gcc/clang extensions, it is a proof that not all ISO members would be happy adopting that extension.
So use C++ already; that’s what it’s for.

Don’t go complaining that C++ is “too complicated” and then be hauling its complexity into C, because all you’ll end up with is a bloated schizophrenic mess that is neither a good C nor a good C++ [alternative].

Please submit a proposal.
That's not an useful response to constructive criticism, and neither is it a good way to deal with user feedback when in a position in a standardization entity.
The best way to think about defer is that all the deferred statements are put in a goto label at the end of the block. When you break/return/whatever, it just jumps to that label first. This just makes slightly more convenient what the standard approach to error handling in C systems programming already is.

Looking at the article, this specific implementation doesn’t quite go all the way, requiring a special guard {} block instead of working anywhere. A better implementation, working in any context, would be easy but has to be baked into the compiler.

Edit: just saw the proposal for inclusion into the C standard. It is unfortunate that they are considering 1) requiring a guard block and 2) deferring clean up to the end of the function instead of end of the scope. #1 is needless syntax and #2 would make the feature useless within loops by causing explosions and contractions in memory usage.

Reading the PDF, I got the impression that the main thing in favor of using a guard keyword is that it would allow it to be implemented as a library. That way it wouldn't require changes to compilers and it might also be possible to adopt into the C++ standard. However, the version without the guard keyword is arguably more ergonomic.
How can you get away from having an explicit guard block?

Without one, you couldn't 'defer' to the end of a containing block from within an if/while/do/for/etc. block.

When/where the deferred code is executed has to be specified some way so an explicit marker for the defer 'scope' is surely required without severely limiting the utility of the feature.

You would just follow the C variable scope rules I think. But I see now how this creates an issue if you want to put the defer, but not the acquisition of the resource it releases, within a control statement.
Starting in page 16, there is a section called "Do we want a guard keyword?" that talks about this. They give an example showing that deferring to the end of the containing block is possible without the guard keyword, although it's a bit wonky, requiring to duplicate the "if". With guard keyword:

    guard {
        void *ptr = malloc(12);
        if (ptr) {
            defer free(ptr);
            // Use ptr
        }
        // Use ptr some more
    } // free ptr here
Without guard keyword:

    {
        void *ptr = malloc(12);
        defer { if (ptr) free(ptr); }
        if (ptr) {
            // Use ptr
        }
        // Use ptr some more
    } // free ptr here
This is a silly example of course because you don't have to check free() for null; calling free(NULL) is a no-op.

In a more realistic case, say some object was handed off so it shouldn't be cleaned up anymore, you can just add a should_cleanup flag and check it in the defer block. There's really no need for guard.

Their other example for guard, running defer in a loop, requires closures. This means it requires dynamic memory allocation and so the complexity goes through the roof.

Why is there a special guard keyword? Scope limits are already clear.
> The guard statement allows for a library implementation. Foregoing the possibility of a library implementation, a possible design choice could be to eliminate the guard statement as it would eliminate the need for an additional reserved keyword and the requirement for programmers to create guarded blocks around deferred statements. If the guard statement is not used, the proposed changes to the behavior of the break statement would likely be eliminated as well (see Appendix K).
Re: Should object values be captured?

The results from 387 responses (to a Twitter poll) show a 2:1 preference for the value being read at the time the deferred statements are executed (66.9%) rather than when the defer statement encountered(33.1%).

Since both options - by value and by reference - may be viewed as reasonable or desirable, neither should be a default. Instead, they both should be using a special syntax. So if you write something likes this -

  guard {
    void * p = malloc(...);
    defer free(p);
  }
it simply won't compile. Instead you'd need to say something like this -

  guard {
    void * p = malloc(...);
    defer free(^p); // evaluate now
  }

  guard {
    void * p = malloc(...);
    defer free(p^); // evaluate later
  }
This may also be reused later for specifying lambda captures... should lambdas ever make their way into C.
The ^ is already taken in context of "C lambdas" though (hoping that if C ever gets lambdas it will simply adopt clang blocks instead of a C++ like syntax):

https://clang.llvm.org/docs/BlockLanguageSpec.html

Aye, I've seen that.

IMO it would've been better (read, cleaner) to merge lambdas and function pointers into a single language construct. Throw in the partial application too and we'd be have a natively supported concept of a "callable" instance -

  void foo(int tick);
  void bar(int tick, int tock);

  void do_something( void (* progress)(int tick) );

  do_something( foo );
  do_something( bar(,1) );
  do_something( void (int tick) { /* lambda */ } );
The syntax is approximate, but for the code that is using callback-based flows this would've been very handy.
problem is, to unify lambdas and function pointers you either break the ABI and use fat pointers or you need to disable W^X which is a security issue.
This will break the ABI indeed. That's given.
It would be easier to execute the argument expressions immediately, as golang does.

If they add this, it would be better to restrict it to the current scope, to avoid forcing the language to create complexity to handle defers created via loop in the background.

Of course, that leads to the question of what to do when you loop over a defer with a goto, which would make sense if you were constantly appending function pointers and arguments to the frame similar to alloca.

That could lead to fun things like a function getting inlined into a for loop and then having its defers that would have been in function instead piled together to blow the stack.

They'll have to make sure they account for that in the spec and implementations.

Mostly, it would be easier to remember that C should not be using such a pattern, and that if you have need of defers and various similar magic to be baked into the language and compiler, what you're doing is probably inappropriate for implementation in the C language in the first place.

Hopefully not with "^" though! Imagine `x^ ^ ^y`...

Why not have `defer free(p)` capture at deferral execution time, and `defer free(capture(p))` capture at statement execution time?

I would prefer an explicit capture at the beginning of the 'defer', as in:

  defer[p] free(p);
Instead of specifying it separately for each variable use.
In its current form, this is really stupid, because of something like this:

    guard {
        for (int i = 0; i < n; i++) {
            defer foo(i);
        }
    }
Now the compiler has to:

1. implement some side of capture/closure mechanism to keep all the 'i's to the end of the guard block

2. do dynamic allocation to store the closures so they can be executed at the end did the scope

1 seems like too much work for such a feature, and 2 is a massive no. Implicit dynamic allocation, in C?

And all of this for nothing. The guard syntax doesn't give any reasonable benefits. They should have just kept it simple; defer happens at the end of the scope, and it just takes 'i' by "reference". It's a shame because it's a feature I would really like to have.

I got the impression that the proposal still has some aspects of the design that are open for discussion, including whether it should be static or dynamic, and whether it should capture the variables by value or by reference. (These are discussed in pages 13-16)
Based on feedback from the committee, I think it's very unlikely a dynamic approach will be used. The static only affects the for loop in terms of whether the deferred statements are run once or for each iteration of the loop, and the only real world use of this I've found so far involves allocating and deallocating resources within each loop iteration which would be supported by the static approach.
They can avoid #2 by doing by-reference capture or they can piggy-back on alloca().

Still, I agree that this is not "nice and clean". Ultimately it doesn't feel like something that belongs in C.

I suspect it will come with some really big caveats. For instance what you wrote shouldn't compile, because i's storage duration doesn't extend to the end of the guard block. If you write

    guard {
        int i;
        for (i=0; i<n; i++) defer foo(i);
    }
I would expect foo(n) to be called n times. That is, variables won't be captured, it would work literally as if you wrote "foo(i)" n times at the end of the guard block. It's a footgun, but everything in C is a footgun and this behaviour is not surprising to me as a C developer.

So I expect the implementation would be

1. If a defer block references variables which do not live to the end of the guard block, the program is malformed (compiler error).

2. Compile each defer block as if it was written at the end of the guard block. What order the defer blocks are stored in is implementation-defined

3. Put a pointer on the stack each time a defer statement executes pointing to the compiled defer statement.

With this each defer statement is just a few bytes of overhead added to the stack and you can do it in a loop, though it may not do what you expect if you come from Go. For the example I expect the compiler to emit code similar to:

    struct defer_node {
        void **label;
        struct defer_node *next;
    };
    struct defer_node *defer_start=0;
    int i;
    for (i = 0; i < n; i++) {
        struct defer_start *defer_new = alloca(sizeof(struct defer_node));
        defer_new->label = &defer_stmt0;
        defer_new->next = defer_start;
        defer_start = defer_new;
    }
    
    while (defer_start) {
        goto *defer_start->label;
    defer_stmt_exit:
        defer_start = defer_start->next;
    }

    return;
    /* or break; or continue; whatever is appropriate for the enclosing block */

    defer_stmt0:
    foo(i);
    goto defer_stmt_exit;
This makes defer more or less just a mechanical transformation of code that can be expressed with existing C primitives and without requiring dynamic memory. I think it would be a good addition to C's structured programming elements.
The proposal poses object value capture as an open question. See heading "Should object values be captured?" on page 14 of the proposal: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2542.pdf
Yes, you are correct and there wasn't much support for capturing values so we would probably only support capture by reference and wait to see if C solves this problem in general by introducing lambdas.
alloca() is not a standard C primitive. It's only an extension, and its purpose is to dynamically allocate memory that is automatically freed. Using a dynamic amount of stack space is dynamic allocation, it's just not on the heap.

This still makes it really easy to overflow your stack, especially if for example your loop count above can come from user input. This is dangerous for all the same reasons that variable-length arrays are dangerous.

> What order the defer blocks are stored in is implementation-defined.

But the order they're executed in would have to be defined, lest we build ourselves another major bug generator.

This isn't really true. If you're capturing by reference, you can easily say that defer can only be called on variables declared in scopes no deeper than the beginning of the defer block and you still have a very useful feature. If you're capturing by value, the "closures" are probably just getting created in the stack like normal auto variables. Either way, this doesn't really seem to be a deal breaker.
Doing defer in a loop is usually a bug in your Go code. Sure there are legit use cases of it, but in ~90% of the time what you really want to do is (in Go, I haven't used C recently so not sure how to do lambdas correctly):

    for i := 0; i < 10; i++ {
      func(i int) {
        defer foo(i)
        // other code
      }(i)
    }
Based on committee discussion, I think it is unlikely we will attempt to capture the values. The capture will most likely be done by reference.

This case of the defer in the loop is frequently cited, probably because it is a problematic case. However, I looked at a lot of real code and the only case I found of resources being allocated in a loop they were allocated at the beginning of the loop and deallocated at the end. Another option we are considering is to use the scope for the guarded block. In this case, deferred statements would be executed at the end of each iteration of the for loop which would be ideal for this sort of code. For example, you could rewrite this function using defer:

https://github.com/openssl/openssl/blob/a829b735b645516041b5...

like this:

    for (;;) {
        raw = 0;
        ptype = 0;
        i = PEM_read_bio(bp, &name, &header, &data, &len);
        defer {
          OPENSSL_free(name);
          name = NULL;
          OPENSSL_free(header);
          header = NULL;
          OPENSSL_free(data);
          data = NULL;
        }
 ...
        } else {
            /* unknown */
        }
    } // end for loop, run deferred statements
Agreed. In Go, defers accumulate and are all called at the end of a function, which I believe is a mistake (but there are practical uses for it). I think it would be much more fitting for C just to have the defer happen at the end of the scope. It would be very easy for compiler devs to implement it too--that above example could be done just by taking the defer block and pasting it at the end of the scope, with zero overhead. More complex control flow can be implemented with a simple goto.
The fact that the loop case is rare does not mean it isn't still problematic. It's creating a new opportunity to shoot yourself in the foot while trying to solve another one. (This is with the separate guard{} block.)

I would strongly agree that the dedicated guard{} block is a bad idea and this should just tie into the innermost scope. I see what this is trying to do ("if (...) { foo.x = malloc(); defer free(foo.x); }") but you don't solve a UI problem (as in, user interface for the programmer to their code) by adding more weird UI.

("Worst" example for shooting yourself in the foot: defer inside macros. Programmer then forgets that the macro contains a defer, and the defer defaults to the function-implicit outer guard block. But it's really in a nested loop. That's gonna be a fun week of debugging... much less of a risk when you have the guarantee in terms of innermost scope.)

Closures capturing iteration variables by reference is a common footgun in many languages unfortunately.
The simple solution to me seems to be to just ban use of loops hierarchically between a defer statement and a guard block. The guard block should definitely be kept as using scope seems like it confuses and complicates the notion of scope; for instance, it seems like a footgun if "if(some condition that turns out to be always true) {blah blah}" can't be refactored into "blah blah". Also, using scope seems to make it difficult to use defer in macros.
That is effectively one of the options that is under discussion. One of the advantage of the approach with defer is really that everything happens in the open and all uses that one would want to classify as misuse can easily be flagged by the compiler.
Some commenters are claiming this issue can be worked around by using by-reference capture, but the following code still requires dynamic allocation:

  void reverse_bitstream(void * read_ctx, void * write_ctx) {
    guard {
      while(has_more_bits(read_ctx)) {
        int b = read_bit(read_ctx);
        if     (b == 0) defer write_bit(write_ctx, 0);
        else if(b == 1) defer write_bit(write_ctx, 1);
      }
    }
  }
An example like that is exactly why, in its current form, it's a bad idea. It's too high level. On the surface it seems like it makes code more consise and elegant, but in reality it just pushes the "ugly" implementation details and allocations onto the compiler. When you start writing code that's phrased in terms of high-level compiler features, rather than in terms of what the computer needs to do, then you're no longer writing C.
No, actually none of that. That code has a constraint violation: it uses the variable i outside of its scope. Even if i would be declared on the same level as the guard, this sequence of deferred statements would not do good, but also not much harm. It would call foo with all the same value for i, namely n.

To have a good example where defer inside a loop actually does something, you'd have to capture the value of i. We also have a macro for that in the reference implementation, but that has a much more specialized scope of use.

Zig has defer and errdefer. C should just steal however Zig does it.
I love Zig, and I'm looking forward to it continuing to mature. I've been waiting for a specific compiler TODO to be resolved (something like initializing union bitfields) that (at least as of 0.7.0) wasn't yet implemented, because I was using it in a toy OS for some GDT stuff.
Had some difficulty with toy os gdt too! I think what you are looking for is the packed union bug.

I would suggest doing it "the hard way", one thought would be treating it as an array (comptime known length) of u8

A very welcomed addition.
void * * a = NULL;

while(TRUE)

{

    a = malloc(sizeof *a);

    if(a == NULL)

        break;

    *a = malloc(1024);

    if(*a == NULL)

        break; 

    if(!do_some_other_tests(a))

        break; 

   return a;
}

/* cleanup /

if(a == NULL)

    return;
if(a != NULL)

    free(*a);

 free(a);

alt:

void * * a = NULL;

switch(TRUE)

{

    defaultt :

    a = malloc(sizeof *a);

    if(a == NULL)

        break;

    *a = malloc(1024);

    if(*a == NULL)

        break; 

    if(!do_some_other_tests(a))

        break; 

   return a;
}

/* cleanup /

if(a == NULL)

    return;
if(a != NULL)

    free(*a);

 free(a);
If the proposed implementation requires a guard clause, then it seems like it is no better than the while (TRUE) implementation above and it's not worth the support in the standard. Maybe it's a bit more structured and covers a few more use-cases, but not worth the standardization.
In the current proposal, the whole function body can act as a guarded block. So for simple cleanup strategies that are bound to functions, you wouldn't need this.
Just use some macros, easy peasy. (gcc computed goto)

    #define DEFER_START(scope_name) void * scope_name = &&scope_name##_end;
    #define DEFER(scope_name, iter_name, func) void * iter_name = scope_name; { \
            scope_name = &&iter_name ##_exe; \
            if (0) { iter_name##_exe: {func} goto *iter_name; } }

    #define DEFER_END(scope_name) scope_name##_start: goto *scope_name; scope_name##_end: {}
    #define DEFER_EXE(scope_name) goto scope_name##_start;

    ...
    {
    DEFER_START(scope);
    void * p = malloc(4);
    if (!p) DEFER_EXE(scope);
    DEFER(scope, free_p, { printf("freep\n"); free(p); });
    void * q = malloc(40);
    if (!q) DEFER_EXE(scope);
    DEFER(scope, free_q, { printf("freeq\n"); free(q); });
    DEFER_END(scope);
    }
you can even macro the free_p/ free_q names with line numbers to shorten the macros, IE DEFER(scope, {});
Add: If you want an easier, and use shadowed variables:

    #define guard_name3(name, line) name ## line
    #define guard_name2(name, line) guard_name3(name, line)
    #define guard_line_name(name) guard_name2(name, __LINE__)
    #define guard { void * defer_scope = &&guard_line_name(defer_scope ##_exe); \
      if (0) { guard_line_name(defer_scope ##_exe) : defer_scope = 0; }\
      if (defer_scope != 0)
    #define guard_end goto * defer_scope; }
    #define guard_break goto *defer_scope;
    #define defer(func) void * guard_line_name(defer_scope_item) = defer_scope; \
      defer_scope = &&guard_line_name(defer_scope_item##_label); \
      if (0) { guard_line_name(defer_scope_item##_label): { func } goto * guard_line_name(defer_scope_item); }

    guard {
      void * p = malloc(10);
      if (!p) guard_break;
      defer({ printf("freep\n"); free(p);});
      void * q = malloc(10);
      if (!q) guard_break;
      defer({printf("freeq\n"); free(q);});
            
      void * fail = 0;
      if (!fail) guard_break;
      defer({printf("freefail\n"); free(fail);});
      guard_end;
    }
I think it's great that the authors are working on this.

RAII is one of the best things about C++, and I'm excited for similar functionality in C. GCC's __cleanup__ is a poor substitute for a fully-baked addition to the language.

From skimming through the paper, it looks like there's an open discussion about the 'guard' keyword and scoping. I know that the scoping rules are tricky.

Would it make sense for defer statements to be attached to a variable's scope instead of a scope block? It would look something like GCC's __cleanup__, except that it could run an arbitrary statement/block instead of a callback. Scope-level defer() could be specified by attaching to a depth number.

If anybody involved in the paper is reading this, what would you think about this syntax?

    //---------- Attaching a defer() to a variable's scope -----------//

    int main(void) {
        int *dummy = malloc(sizeof(int));

        defer (dummy) {
            printf("This statement prints second.\n");
            free(dummy);
        }
        
        printf("This statement prints first.\n");
    }

    //------- Attaching a defer() to the current block's scope -------//
    
    int main(void) {
        int *dummy;

        do {
            dummy = malloc(sizeof(int));

            defer (0) {
                printf("This statement prints second.\n");
                free(dummy);
            }

            printf("This statement prints first.\n");
        } while (0);

        printf("This statement prints third.\n");
    }

    //-------- Attaching a defer() to a parent block's scope ---------//
    
    int main(void) {
        int *dummy;

        do {
            do {
                dummy = malloc(sizeof(int));

                defer (1) {
                    printf("This statement prints third.\n");
                    free(dummy);
                }

                printf("This statement prints first.\n");
            } while (0);

            printf("This statement prints second.\n");
        } while (0);

        printf("This statement prints fourth.\n");
    }

This syntax would eliminate the need for an explicit guard keyword, and would also make for a straightforward porting process for all code that currently uses __cleanup__. It also feels a little more C-like to me, in that it resembles the look-and-feel of other control-flow statements.

In my example, defer() with a variable-name would attach itself to the variable's scope, and would execute when the variable leaves scope.

And defer() with an integer would attach itself to [current_scope_level - target_value]. So a defer(0) would trigger at the end of the current block scope, and defer(1) would trigger at the end of the parent block scope. Combined with generic {} blocks, you could get the same behavior provided by the paper's suggested guard keyword.

While C++ has made RAII concepts mainstream, you will find it in a couple of lesser known languages as well of similar age.
The panic/recover mechanism would be a disaster. C code is not written with the possibility of stack unwinding in mind and this introduces the possibility of non-visible, non-local control flow at every function call. The simple possibility of that would massively hurt codegen. One of the benefits of writing in C over C++ is that you don’t have to worry about non-local control flow like exceptions.
“What we are trying to accomplish…”

See also: silk purse, sow’s ear.

We detached this comment from https://news.ycombinator.com/item?id=25420221.

Please don't post shallow or snarky comments to HN [1]. It is particularly damaging when talking to a genuine expert [2]. Having a user like rseacord commenting in threads like this is great for everybody. When you incentivize them to leave, you harm the entire community. Your other comment [3] was pretty insulting too (at the beginning); people don't serve on standards committees because of money.

[1] https://news.ycombinator.com/newsguidelines.html

[2] https://news.ycombinator.com/item?id=22865357

[3] https://news.ycombinator.com/item?id=25420638

Edit: we've already had to ask you to stop posting in the flamewar style to HN, and you've been doing it repeatedly lately. Would you please review the guidelines and use HN as intended? I don't want to ban you.

Honestly mate, just go ahead and delete my account.
Still here. Delete my account.
Delete my account, please.

I do not want to have to ask again.

The astonishing thing here is that there is a forum where papers on C hacks can still be considered for publication.

I some ways that is encouraging, because research in practical systems things is valuable and used to be one of the cornerstones of computer science.

If you want these sort of services, why use C?
Sometimes you have to use C because there are no other options, especially for embedded systems which have no (e.g.) LLVM support.
There is a lot of legacy C code out there.
UNIX clones are not going to get rewritten into something else, so we need to improve it.
I can't say anything about whether this is a good idea in general or not, but if they are going to do it, then why not just combine the allocation with the defered free into a single syntax ...

'void* x = defer_free_malloc (...);'

I came to say this too, it seemed like an obvious evolution. I find the "randomness" of the defer-statement a bit scary, like there is too much freedom. There's no guarantee that you're deferring the proper code to free the resource, which somehow should be the default.

Not at all sure of the proper syntax, but at least tying it together with something like

    void * const x = DEFER(malloc(...), free);
would make sense. In a more high-level language with interfaces, there should be a way to tie together the creation and destruction into a combined type, and just do

    obj = acquire SomeType(...);
And then 'acquire' should take care that the type to the right implements the interface, and automatically add a deferred call to the proper freeing method. Hm. I guess I just invented some kind of garbage collection, bummer.
(comment deleted)
This is super cool! I want this!
Just use the reference implementation that is linked in the post. We would much appreciate feedback from users at this point.
This feature have the potential to remove some christmas trees (nested if statements) in MISRA C compatible code. Publishers should have waited to January! :)