This case of the defer in the loop is frequently cited, probably because it is a problematic case. However, I looked at a lot of real code and the only case I found of resources being allocated in a loop they were allocated at the beginning of the loop and deallocated at the end. Another option we are considering is to use the scope for the guarded block. In this case, deferred statements would be executed at the end of each iteration of the for loop which would be ideal for this sort of code. For example, you could rewrite this function using defer:
https://github.com/openssl/openssl/blob/a829b735b645516041b5...
like this:
for (;;) {
raw = 0;
ptype = 0;
i = PEM_read_bio(bp, &name, &header, &data, &len);
defer {
OPENSSL_free(name);
name = NULL;
OPENSSL_free(header);
header = NULL;
OPENSSL_free(data);
data = NULL;
}
...
} else {
/* unknown */
}
} // end for loop, run deferred statementsI would strongly agree that the dedicated guard{} block is a bad idea and this should just tie into the innermost scope. I see what this is trying to do ("if (...) { foo.x = malloc(); defer free(foo.x); }") but you don't solve a UI problem (as in, user interface for the programmer to their code) by adding more weird UI.
("Worst" example for shooting yourself in the foot: defer inside macros. Programmer then forgets that the macro contains a defer, and the defer defaults to the function-implicit outer guard block. But it's really in a nested loop. That's gonna be a fun week of debugging... much less of a risk when you have the guarantee in terms of innermost scope.)
Still, I agree that this is not "nice and clean". Ultimately it doesn't feel like something that belongs in C.
for i := 0; i < 10; i++ {
func(i int) {
defer foo(i)
// other code
}(i)
}To have a good example where defer inside a loop actually does something, you'd have to capture the value of i. We also have a macro for that in the reference implementation, but that has a much more specialized scope of use.
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.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.
But the order they're executed in would have to be defined, lest we build ourselves another major bug generator.
void reverse_bitstream(void * read_ctx, void * write_ctx) {
guard {
while(has_more_bits(read_ctx)) {
int b = read_bit(read_ctx);
if (b == 0) defer write_bit(write_ctx, 0);
else if(b == 1) defer write_bit(write_ctx, 1);
}
}
}
1. implement some side of capture/closure mechanism to keep all the 'i's to the end of the guard block
2. do dynamic allocation to store the closures so they can be executed at the end did the scope
1 seems like too much work for such a feature, and 2 is a massive no. Implicit dynamic allocation, in C?
And all of this for nothing. The guard syntax doesn't give any reasonable benefits. They should have just kept it simple; defer happens at the end of the scope, and it just takes 'i' by "reference". It's a shame because it's a feature I would really like to have.