Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

My idea was to add something like the GoLang defer statement to C (as a function with some special compiler magic). The following is an example of how such a function could be used to cleanup allocated resources regardless of how a function returned:

  int do_something(void) {
    FILE *file1, *file2;
    object_t *obj;
    file1 = fopen("a_file", "w");
    if (file1 == NULL) {
      return -1;
    }
    defer(fclose, file1);
  
    file2 = fopen("another_file", "w");
    if (file2 == NULL) {
      return -1;
    }
    defer(fclose, file2);

    obj = malloc(sizeof(object_t));
    if (obj == NULL) {
      return -1;
    }
    // Operate on allocated resources
    // Clean up everything
    free(obj);  // this could be deferred too, I suppose, for symmetry 
  
    return 0;
  }


Golang gets this wrong. It should be scope-level not function-level (or perhaps there should be two different types, but I have never personally had a need for a function-level cleanup).

Edit: Also please review how attribute cleanup is used by existing C code before jumping into proposals. If something is added to C2x which is inconsistent with what existing code is already doing widely, then it's no help to anyone.


Yes, we have discussed adding this feature at scope level. A not entirely serious proposal was to implement it as follows:

  #define DEFER(a, b, c)  \
     for (bool _flag = true; _flag; _flag = false) \
     for (a; _flag && (b); c, _flag = false)

  int fun() {
     DEFER(FILE *f1 = fopen(...), (NULL != f1), mfclose(f1)) {
       DEFER(FILE *f2 = fopen(...), (NULL != f2), mfclose(f2)) {
         DEFER(FILE *f3 = fopen(...), (NULL != f3), mfclose(f3)) {
             ... do something ...
         }
       }
     }
  }
We are also looking at the attribute cleanup. Sounds like you should be involved in developing this proposal?


Apropos of this, I'll toss in: please support do-after statements (and also let statements).

  do foo(); _After bar();
  /* exactly equivalent to (with gcc ({})s): */
  ({ bar(); foo(); });
  #define DEFER(a, b, c) \
    _Let(a) if(!b) {} else do {c;} _After
(This is in fact a entirely serious proposal, though I don't actually expect it to happen.)


Yes, I'll ask around in Red Hat too, see if we can get some help with this.


Would it make sense for defer to operate on a scope-block, sort of like an if/do/while/for block instead?

That would allow us to write:

   defer close(file);
or:

   defer {
      release_hardware();
      close(port);
   }
I feel like that syntax fits very nicely with other parts of C, and could even potentially lend itself well to some very subtle/creative uses.

I feel like a very C-like defer would:

   - Trigger at the exit of the scope-level where it was declared.

   - Be able to defer a single statement, or a scope-block.

   - Be nestable, since a defer statement just runs its target
     scope-block at the exit of the scope-block where it's defined.

   - Run successive defer statements in LIFO order, allowing later
     defer statements to still use resources that will be cleaned up
     by the earlier ones.


Cleanup on function return is not enough, it needs to be scope exit. We're using this for privilege raising/dropping (example posted above) and also mutex acquisition/release. Both of these really "want" it on the scope level.


Go-like defer() is easily implementable for C using the asm() keyword. Here's an example of how it can be done for x86: https://gist.github.com/jart/aed0fd7a7fa68385d19e76a63db687f...


That's quite an achievement, but you've got to realise that hacks which overwrite the stack return address are not maintainable and likely wouldn't work except for a narrow range of compilers (and even specific versions of those compilers with specific options). It also won't work with stack hardening.

Also it's function-level (like golang) not scope-level (like attribute cleanup). As argued elsewhere in the this thread, golang got this wrong.

Also also, overwriting the return address on the stack kills internal CPU optimizations that both Intel and AMD do for branch prediction.


Maintainable is a point of view. Works fine w/ -fstack-protector for me.

Saying it supports a narrow range of compilers is like saying US two-party system only supports a narrow range of registered voters. Libertarians and Greens absolutely deserve inclusion. They can vote but the system doesn't go out of its way to make life as exciting as possible for them. The above Gist caters to GCC/Clang. Folks who use MSVC, Watcom, etc. absolutely deserve to be supported. The Clang compiled modules can be run through something like objconv and linked into their apps.

Not convinced about scope-level. Supporting docs would help. Sounds like you just want mutex. I'm not sure if I can comment since I can't remember if I've ever written threaded code in C/C++. I would however sheepishly suggest anyone wanting that consider Java due to (a) literally has a class named Phaser come on so cool (b) postmortems I read by webmaster where I once worked.

Not concerned about microoptimizations. All I really wanted was to be able to say stuff like

    const char *s = gc(xasprintf("%s%s", a, b));
Also folks who use those new return trapping security flags might see the branch predictor side-effects as a benefit. Could this really be GC for C with Retpoline for free? I don't know. You decide.




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

Search: