Live data from Hacker News

A Defer Mechanism for C

gustedt.wordpress.com

111–120 of 248 posts

Re: A Defer Mechanism for C

#111
I think it's great that the authors are working on this.

RAII is one of the best things about C++, and I'm excited for similar functionality in C. GCC's __cleanup__ is a poor substitute for a fully-baked addition to the language.

From skimming through the paper, it looks like there's an open discussion about the 'guard' keyword and scoping. I know that the scoping rules are tricky.

Would it make sense for defer statements to be attached to a variable's scope instead of a scope block? It would look something like GCC's __cleanup__, except that it could run an arbitrary statement/block instead of a callback. Scope-level defer() could be specified by attaching to a depth number.

If anybody involved in the paper is reading this, what would you think about this syntax?

    //---------- Attaching a defer() to a variable's scope -----------//

    int main(void) {
        int *dummy = malloc(sizeof(int));

        defer (dummy) {
            printf("This statement prints second.\n");
            free(dummy);
        }
        
        printf("This statement prints first.\n");
    }

    //------- Attaching a defer() to the current block's scope -------//
    
    int main(void) {
        int *dummy;

        do {
            dummy = malloc(sizeof(int));

            defer (0) {
                printf("This statement prints second.\n");
                free(dummy);
            }

            printf("This statement prints first.\n");
        } while (0);

        printf("This statement prints third.\n");
    }

    //-------- Attaching a defer() to a parent block's scope ---------//
    
    int main(void) {
        int *dummy;

        do {
            do {
                dummy = malloc(sizeof(int));

                defer (1) {
                    printf("This statement prints third.\n");
                    free(dummy);
                }

                printf("This statement prints first.\n");
            } while (0);

            printf("This statement prints second.\n");
        } while (0);

        printf("This statement prints fourth.\n");
    }

This syntax would eliminate the need for an explicit guard keyword, and would also make for a straightforward porting process for all code that currently uses __cleanup__. It also feels a little more C-like to me, in that it resembles the look-and-feel of other control-flow statements.

In my example, defer() with a variable-name would attach itself to the variable's scope, and would execute when the variable leaves scope.

And defer() with an integer would attach itself to [current_scope_level - target_value]. So a defer(0) would trigger at the end of the current block scope, and defer(1) would trigger at the end of the parent block scope. Combined with generic {} blocks, you could get the same behavior provided by the paper's suggested guard keyword.

Re: A Defer Mechanism for C

#112

Just use some macros, easy peasy. (gcc computed goto) #define DEFER_START(scope_name) void * scope_name = &&scope_name##_end; #define DEFER(scope_name, iter_name, func) void * iter_name = scope_name; { \ scope_name = &&iter_name ##_exe; \ if (0) { iter_name##_exe: {func} goto *iter_name; } } #define DEFER_END(scope_name) scope_name##_start: goto *scope_name; scope_name##_end: {} #define DEFER_EXE(scope_name) goto sco…

Add: If you want an easier, and use shadowed variables:

    #define guard_name3(name, line) name ## line
    #define guard_name2(name, line) guard_name3(name, line)
    #define guard_line_name(name) guard_name2(name, __LINE__)
    #define guard { void * defer_scope = &&guard_line_name(defer_scope ##_exe); \
      if (0) { guard_line_name(defer_scope ##_exe) : defer_scope = 0; }\
      if (defer_scope != 0)
    #define guard_end goto * defer_scope; }
    #define guard_break goto *defer_scope;
    #define defer(func) void * guard_line_name(defer_scope_item) = defer_scope; \
      defer_scope = &&guard_line_name(defer_scope_item##_label); \
      if (0) { guard_line_name(defer_scope_item##_label): { func } goto * guard_line_name(defer_scope_item); }

    guard {
      void * p = malloc(10);
      if (!p) guard_break;
      defer({ printf("freep\n"); free(p);});
      void * q = malloc(10);
      if (!q) guard_break;
      defer({printf("freeq\n"); free(q);});
            
      void * fail = 0;
      if (!fail) guard_break;
      defer({printf("freefail\n"); free(fail);});
      guard_end;
    }

Re: A Defer Mechanism for C

#113
post #107

Earlier quoted context omitted.

I do feel that RAII is a little bit ruined by the fact that you cannot declare anonymous object instances. For example, I cannot write: lock (mutex); Expecting to declare an anonymous lock with the mutex passed in, as this gets parsed as a declaration of a lock called mutex (hopefully lock doesn’t have a default constrictor and I at least get an error). Instead I have to bake my lock: lock l(mutex); Often I have obje…

IIRC, the problem with C++ is that you can declare anonymous object instances... With surprising results (the object is destroyed at the semicolon, instead of being destroyed at the end of the block).

It is quite dangerous. IIRC some compilers do catch the issue and warn about it.

Re: A Defer Mechanism for C

#114

Earlier quoted context omitted.

The ^ is already taken in context of "C lambdas" though (hoping that if C ever gets lambdas it will simply adopt clang blocks instead of a C++ like syntax): https://clang.llvm.org/docs/BlockLanguageSpec.html

Aye, I've seen that. IMO it would've been better (read, cleaner) to merge lambdas and function pointers into a single language construct. Throw in the partial application too and we'd be have a natively supported concept of a "callable" instance - void foo(int tick); void bar(int tick, int tock); void do_something( void (* progress)(int tick) ); do_something( foo ); do_something( bar(,1) ); do_something( void (int ti…

problem is, to unify lambdas and function pointers you either break the ABI and use fat pointers or you need to disable W^X which is a security issue.

Re: A Defer Mechanism for C

#115
post #106
post #23

Earlier quoted context omitted.

Deferred actions are not explicit at the exit points. That's quite implicit. You can no longer reason about a local piece of code; you now have to know its lexical nesting up to top level to see if it's inside a guard block that might trigger hidden behavior.

It's as much hidden behaviour as destructors being called magically on an object that goes out of scope. In practice it doesn't hinder understandability as much as you think.

> destructors being called magically on an object that goes out of scope

Which C doesn't have.

Re: A Defer Mechanism for C

#116
post #35
post #23

Earlier quoted context omitted.

Deferred actions are not explicit at the exit points. That's quite implicit. You can no longer reason about a local piece of code; you now have to know its lexical nesting up to top level to see if it's inside a guard block that might trigger hidden behavior.

That's still local at the scope level though, which is quite acceptable. Plus, to handle the free or the leak if you had forgotten to free a resource at the exit point would also require to "know its lexical nesting up to top level". Between goto and longjump and co, C has much worse non-local behavior than defer.

Goto isn't nonlocal. You know you'll only every jump from them, and that you'll only ever jump to the specified label.

Re: A Defer Mechanism for C

#117
post #71
post #52

Earlier quoted context omitted.

> As an example of my point, I would refer to the 'garbage collection' issues of a language like 'java' Two languages could hardly be less alike than Java and C++ - the latter is not garbage collected and where destructors are called is completely predictable.

You’re missing the point. The two are alike in that something significant is happening that you as the programmer did not explicitly tell it to do. The specific mechanism really isn’t an important distinction here.

> something significant is happening that you as the programmer did not explicitly tell it to do.

when I put some object on automatic storage in C++ I do so because I explicitely want it to go away when its scope is left (either by reaching } or through an exception)

Re: A Defer Mechanism for C

#118

Earlier quoted context omitted.

Despite being convenient,I have the feeling that it might be a bad idea. What is "I am thinking of using C?"

Depending on your platform, it may be your only reasonable choice.

I agree completely. For the most part it seems if a person has to think long and hard about it, they probably should not use C and if it is the obvious choice that’s probably because C is ordinary in the context. These days there are probably fewer edge cases than in the past.

Re: A Defer Mechanism for C

#119
post #23

Earlier quoted context omitted.

Deferred actions are not explicit at the exit points. That's quite implicit. You can no longer reason about a local piece of code; you now have to know its lexical nesting up to top level to see if it's inside a guard block that might trigger hidden behavior.

it's control flow. What you are saying is like saying "while loops are implicit: you can no longer reason about code; you now must think about the state of the check boolean to see if it will trigger the hidden behaviour of going back to the top of the loop because there's no explicit "go back to the top of the block" statement.

With a while loop, to know what happens at the bottom of the block, you only need to check the top of the block. With defer, to know what happens at the bottom of the block, you need to check the entire contents of the block.

Re: A Defer Mechanism for C

#120
post #54

I’ve always considered “defer” an inelegant kludge in comparison to RAII for automatic cleanup of resources. It’s surprising that the C standards committee is considering adding that to the language. Then again, nearly none of the syntactic C features post-C89 have been very compelling or widely adopted.

RAII is a special case of defer, due to how it abuses constructors and destructors to hook into the points of entering and exiting scope. You could write a RAII implementation in terms of defer but not the other way around. RAII doesn't help with a generic pair of init and deinit functions, such as malloc() and free() for example, unless you wrap your mallocs into "memory objects" or something. You can't do anything…

> You could write a RAII implementation in terms of defer but not the other way around.

c'mon, here's the article from 2000 that introduces ScopeGuard : https://www.drdobbs.com/cpp/generic-change-the-way-you-write...

Post reply on HN