Hacker News new | past | comments | ask | show | jobs | submit login
Braiding the spaghetti: implementing defer in the preprocessor (gustedt.wordpress.com)
41 points by ingve 58 days ago | hide | past | favorite | 14 comments



If you want to use RAII in C, just use the "cleanup" attribute of gcc.

https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute...


*in non portable non standard C


Supported by both gcc and clang, so portable enough for most people. I also like glib's cleanup attribute wrappers: https://docs.gtk.org/glib/auto-cleanup.html.


It is non portable, that's true. But defer [1] is at least undergoing discussions w/the standard committee. Didn't make it into C23 but maybe next time.

[1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm


That's not very practical if you have several operations to perform, you'd have to create a specialized function each time. So no, that extension is not an alternative to designing a new flow control feature. (And it is not new, other languages have it already.)


>I also have some ideas how to make this defer implementation work with break and continue, but that is unfortunately a bit more nasty.

This is the kind of things better done in the compiler, I implemented the n3199 variant of defer[1], along with [[gnu::cleanup]], in a small C compiler with about 200 LOC by extending the VLA de-allocation algorithm, the process is archived at [2].

[1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3199.htm [2] https://github.com/fuhsnn/slimcc-defer/commits/


Thanks for the pointer! Definitively, on the long run these things should go into compilers directly, yes.

BTW, the break continue stuff now works in the development branch.

I notice that in that implementation there is an addition to the compiler state with indirections, whereas in ellipsis all is done with just three local variables; two per-function for knowing if we have to unwind completely and for the return value, and one per loop construct to know if we have to unwind there. All the rest is static information about nestedness and lexicographic ordering of code that can be deduced very early (here in the preprocessor). The result of all of this is a very organized braid of gotos, that in general are mostly optimized out.


I love the idea of having a defer statement in general for C, but given the complexity of C scopes it's a little hard to wrangle (switch statements in particular break a lot of assumptions about how scopes should work).

I would prefer a directly scoped syntax similar for a for statement, something like

   defer (void * p = malloc(17); free(p)) {
     ...
   }
This gets more cumbersome as you have more such scopes in a function, but it gives a sane bounding. You can sort of do this now with a properly constructed for loop so that it cleans up on regular exit from the loop, but it can't handle exception exits (returns, breaks, and god forbid goto or longjmp).


This would be much more restrictive that what is proposed, because usually you would add `defer` statements as you go in a block, when you allocate more resources for example.

Also usually you should be able to have several actions,

  defer { 
    one; 
    two; 
  }
And the bounding in the proposed feature is sane, I think, it is the surrounding compound statement (AKA `{ ... }` block).


shameless plug for my defer header: https://github.com/aedrax/defer.h


You can implement an acceptable defer with the standard preprocessor and some switch abuse.

The only annoying part is needing to use "defer_return" and such instead of the proper keywords.

Unlike most defer implementations for C this doesn't need a function-scope fixed sized block, it's all properly scoped, the switch effectively models a state machine. Similar tricks can be used to implement yield and such.


How would the proposed solution work with this code?

  void foo() {
    char *p = malloc(...);
    defer free(p);
    ...
    {
      FILE *p = fopen(...);
      defer fclose(p);
      ...
    }
    return;
  }
Would it run both deferred statements, each with the correct argument?


Yes, the visibility rules for variables remain exactly the same. The dependent statement of a defer lives in the same scope as the defer is placed.


the amount of effort going into people either:

- straightup forgetting to free shit - writing horrible to read code making it impossible to do cleanup or track allocations

why try to make c into c++ or rust? those languages already exist.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: