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

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

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

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.