A defer mechanism for C

An overview of a paper that presents a reference implementation of a defer mechanism (co-authored with Robert C. Seacord) has been accepted for publication at SAC’21. The full version is available as a research report on the HAL archive:

https://hal.inria.fr/hal-03090771/document

It introduces the implementation-side of a C language mechanism for error handling and deferred cleanup adapted from similar features in the Go programming language. Here is a simple example that uses such a feature:

guard {
  void * const p = malloc(25);
  if (!p) break;
  defer free(p);
  void * const q = malloc(25);
  if (!q) break;
  defer free(q);
  if (mtx_lock(&mut)==thrd_error) break;
  defer mtx_unlock(&mut);
  // all resources acquired
  // use p, q, and mut until the end of the block
  ...
}

This mechanism improves the proximity, visibility, maintainability, robustness, and security of cleanup and error handling over existing language features. The library implementation of the features described by this paper is publicly available under an Open Source License at https://gustedt.gitlabpages.inria.fr/defer/.

This feature is under consideration for inclusion in the C Standard and has already been discussed on the last WG14 meeting. The main introduction can be found in a paper written together with Alex Gilding, Tom Scogland, Robert C. Seacord, Martin Uecker, and Freek Wiedijk:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2542.pdf

11 thoughts on “A defer mechanism for C”

  1. Very nice.
    May I suggest something.. I think if we had a if with + initializer + defer the code
    would easier to understand in many circumstances and this can be a syntax complement
    for the defer mechanism.

    For instance:

      
    int main() {
    
    if (void * const p = malloc(25); p; free(p))  {
        if (void * const q = malloc(25); q; free(q))  {
          if (int er = mtx_lock(&mut); er != thrd_error;  mtx_unlock(&mut))  {
    
      // all resources acquired
    
      }
    }
    

    }
    }

    One advantage is the scope. For instance:

      
    if (void * const p = malloc(25); p; free(p))  {
         // using  p and p is not null
      }
      //p cannot be used here
    
    1. I have played a lot with this kind of extensions, in P99 some of them are explored. Maybe some day we will be able to integrate if variables into C as they already exist in C++, see e.g the C/C++ core, but otherwise C doesn’t do inventions and only standardizes existing practice.

      1. C++ have “if with initializers” and C could have it as well, It would be nice to have…

        But by suggestion here is “if with initializer PLUS defer” something that C++
        doesn’t need because it has destructors.

        Without exceptions the idiom in C is most of the time check the acquisition
        using if and then free resources at the end of scope.

        Current code:

        FILE * f = fopen(“f.txt”, “r”);
        if (f) {

        fclose(f);
        }

        Using if + initializer + defer

        if (FILE * f = fopen(“f.txt”, “r”); f; fclose(f)) {

        }
        //f cannot be used here…

        Using defer: (Is this dynamic instead of static?)

        FILE * f = fopen(“f.txt”, “r”);
        if (f) {
        defer (fclose(f));
        }

        //f is at the scope but should not be used

        1. In fact, the defer mechanism has several features that your idea is missing. First, deferred statements are triggered by other events than leaving the scope, e.g by exit or a panic that is triggered by a signal. Then, defer can be used for other statements than simple function calls on an object (like a pointer or mutex) and it can even be a whole compound statement. I hate it, if I have to invent a function with a random name, far from its actual use, just to do some cleanup. Or even a whole dummy class with constructors and destructiors in C++. Finally, if you have several cascaded features to clean up, in the example there are already three, you’d have use nested scopes for all of these. This gets a bit nasty (with indentation to maintain) if you add or remove such features. To remove a defer maintained resource, you just remove the corresponding statements.

  2. Looks a useful feature to have, GCC currently has a similar “cleanup” variable attribute that allows a function to be run when a variable goes out of scope.

    1. Maybe if you restrict the use of defer to the use of freeing a pointer, yes. But these are only examples. As you can see with the mutex lock/unlock mechanism, defer has a much broader set of use cases. Another example could be closing a html tag in a log file, writing 0 to an IO port, really any action that you want to perform and that should even be guaranteed on exit, thrd_exit, return or in which way you terminate the actual context.

      1. I agree with all your points.. In fact my suggestion is a complement for syntax and not a replacement.
        If I had these two syntax options I am not sure which one would be the most common in my code.

      2. I don’t see your point at all. I use the g_autoptr feature for all these things every day.

        For example, here is a sample that always ensures a transaction is closed at function exit (error or not):
        https://github.com/flatpak/flatpak/blob/master/common/flatpak-utils-private.h#L723

        Sample use here:
        https://github.com/flatpak/flatpak/blob/master/common/flatpak-utils.c#L5778

        Or this one that always ensures a temporary file is unlinked:
        https://gitlab.gnome.org/GNOME/libglnx/-/blob/master/glnx-fdio.h#L79

  3. Very good to see RAII-like concepts being considered for C! I have been using an object registry I wrote to free dynamic memory objects at function exit via a handful of routines offered by the registry (not using GCC’s cleanup variable attribute). It can be (ab)used to release non-pointer resources but doing so isn’t portable.

    The registry’s C API is built around void* so I wrap that with a Macro API to re-introduce type-safety. Incidentally the Macro API uses many of the tricks found in Jens’ P99 library, IS_EMPTY for example is fantastic! (I only wish P99 had an IS_DEFINED variadic macro for testing the defined-ness of other macros, but if Jens didn’t include it in P99, then it’s probably impossible.)

Comments are closed.