September 12th, 2024

Braiding the spaghetti: implementing defer in the preprocessor

The blog post discusses a "defer" mechanism in C for improved resource management, simplifying cleanup code execution, enhancing readability, and addressing limitations in handling return types and control statements.

Read original articleLink Icon
Braiding the spaghetti: implementing defer in the preprocessor

The blog post discusses the implementation of a "defer" mechanism in the C programming language, aimed at improving resource management and cleanup in code. The traditional approach often leads to complex and error-prone patterns, such as using `goto` statements for cleanup, which can make code difficult to read and maintain. The proposed defer feature allows developers to specify cleanup actions that will be executed at the end of a compound statement, thus keeping the cleanup code close to where resources are allocated. The implementation leverages preprocessor extensions to manage the complexity of label generation and control flow, allowing for a more straightforward coding style. The author notes that while the current implementation in the eĿlipsis preprocessor has some limitations, such as handling return types and certain control statements, it represents a significant step towards cleaner and more maintainable C code. The blog emphasizes that modern compilers can handle the complexity introduced by the preprocessor, making the code efficient while providing valuable feedback for static analysis.

- The "defer" mechanism simplifies resource management in C by executing cleanup code at the end of a compound statement.

- Traditional cleanup patterns using `goto` can lead to complex and error-prone code.

- The implementation uses preprocessor extensions to manage label generation and control flow.

- Current limitations include handling non-void return types and certain control statements.

- The approach aims to enhance code readability and maintainability while remaining efficient for compilers.

Link Icon 7 comments
By @pif - about 1 month
If you want to use RAII in C, just use the "cleanup" attribute of gcc.

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

By @fuhsnn - 29 days
>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/

By @andrewla - 30 days
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).
By @aedrax - about 1 month
shameless plug for my defer header: https://github.com/aedrax/defer.h
By @sirwhinesalot - 30 days
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.

By @teo_zero - about 1 month
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?
By @sim7c00 - 30 days
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.