Hacker News new | ask | show | jobs
by Asooka 2017 days ago
I suspect it will come with some really big caveats. For instance what you wrote shouldn't compile, because i's storage duration doesn't extend to the end of the guard block. If you write

    guard {
        int i;
        for (i=0; i<n; i++) defer foo(i);
    }
I would expect foo(n) to be called n times. That is, variables won't be captured, it would work literally as if you wrote "foo(i)" n times at the end of the guard block. It's a footgun, but everything in C is a footgun and this behaviour is not surprising to me as a C developer.

So I expect the implementation would be

1. If a defer block references variables which do not live to the end of the guard block, the program is malformed (compiler error).

2. Compile each defer block as if it was written at the end of the guard block. What order the defer blocks are stored in is implementation-defined

3. Put a pointer on the stack each time a defer statement executes pointing to the compiled defer statement.

With this each defer statement is just a few bytes of overhead added to the stack and you can do it in a loop, though it may not do what you expect if you come from Go. For the example I expect the compiler to emit code similar to:

    struct defer_node {
        void **label;
        struct defer_node *next;
    };
    struct defer_node *defer_start=0;
    int i;
    for (i = 0; i < n; i++) {
        struct defer_start *defer_new = alloca(sizeof(struct defer_node));
        defer_new->label = &defer_stmt0;
        defer_new->next = defer_start;
        defer_start = defer_new;
    }
    
    while (defer_start) {
        goto *defer_start->label;
    defer_stmt_exit:
        defer_start = defer_start->next;
    }

    return;
    /* or break; or continue; whatever is appropriate for the enclosing block */

    defer_stmt0:
    foo(i);
    goto defer_stmt_exit;
This makes defer more or less just a mechanical transformation of code that can be expressed with existing C primitives and without requiring dynamic memory. I think it would be a good addition to C's structured programming elements.
3 comments

alloca() is not a standard C primitive. It's only an extension, and its purpose is to dynamically allocate memory that is automatically freed. Using a dynamic amount of stack space is dynamic allocation, it's just not on the heap.

This still makes it really easy to overflow your stack, especially if for example your loop count above can come from user input. This is dangerous for all the same reasons that variable-length arrays are dangerous.

The proposal poses object value capture as an open question. See heading "Should object values be captured?" on page 14 of the proposal: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2542.pdf
Yes, you are correct and there wasn't much support for capturing values so we would probably only support capture by reference and wait to see if C solves this problem in general by introducing lambdas.
> What order the defer blocks are stored in is implementation-defined.

But the order they're executed in would have to be defined, lest we build ourselves another major bug generator.