3 ms·
Can 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 g
by ozgrakkurt 8mo ago
Can 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.
- ozgrakkurt 8mo agoI found that some error prone cases are harder to express with defer in zig. For example if I have a ffi function that transfers the ownership of some allocator in the middle of the function.
- kev009 8mo agoIt allows you to put the deferred logic near the allocation/use site which I noticed was helpful in Go as it becomes muscle memory to do cleanup as you write some new allocation and it is hinted by autocomplete these days. But it adds a new dimension of control flow, which in a garbage collected language like Go is less worrisome whereas in C this can create new headaches in doing things in the right order. I don't think it will eliminate goto error handling for complex cases.
- mort96 8mo agoI feel like C people, out of anyone, should respect the code gen wins of defer. Why would you rely on runtime conditional branches for everything you want cleaned up, when you can statically determine what cleanup functions need to be called? In any case, the biggest advantage IMO is that resource acquisition and cleanup are next to each other. My brain understands the code better when I see "this is how the resource is acquired, this is how the resource will be freed later" next to each other, than when it sees "this is how this resource is acquired" on its own or "this is how the resource is freed" on its own. When writing, I can write the acquisition and the free at the same time in the same place, making me very unlikely to forget to free something.
- qsera 8mo ago>"this is how the resource is acquired, this is how the resource will be freed later" Lovely fairy tale. Now can you tell me how you love to scroll back and examine all the defer blocks within a scope when it ends to understand what happens at that point?
- sparkie 8mo agoDefer might be better than nothing, but it's still a poor solution. An obvious example of a better, structural solution is C#'s `using` blocks. using (var resource = acquire()) { } // implicit resource.Dispose(); While we don't have the same simplicity in C because we don't use this "disposable" pattern, we could still perhaps learn something from syntax and use a secondary block to have scoped defers. Something like: using (auto resource = acquire(); free(resource)) { } // free(resource) call inserted here. That's no so different to how a `for` block works: for (auto it = 0; it < count; it++) { } // automatically inserts it++; it < count; and conditional branch after secondary block of for loop. A trivial "hack" for this kind of scoped defer would be to just wrap a for loop in a macro: #define using(var, acquire, release) \ auto var = (acquire); \ for (bool var##_once = true; var##_once; var##_once = false, (release)) using (foo, malloc(szfoo), free(foo)) { using (bar, malloc(szbar), free(bar)) { ... } // free(bar) gets called here. } // free(foo) gets called here.
- uecker 8mo agoThe advantage is that it automatically adds the cleanup code to all exit paths, so you can not forget it for some. Whether this is really that helpful is unclear to me. When we looked at defer originally for C, Robert Seacord hat a list of examples and how the looked before and after rewriting with defer. At that point I lost interest in this feature, because the new code wasn't generally better in my opinion. But people know it from other languages, and seem to like it, so I guess it is good to have it also in C.
- ozgrakkurt 8mo agoThanks, seems like the document is this one http://robertseacord.com/wp/2020/09/10/adding-a-defer-mechanism-to-c/ http://robertseacord.com/wp/2020/09/10/adding-a-defer-mechan...
- uecker 8mo agoIf I remember correctly he had much more examples somewhere, but I am not sure this was ever shared in public.