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;
}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.
#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? 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.)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.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.
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.FWIW, I don't think it would wind up being spelled with attribute syntax because we would likely want programmers to have a guarantee that the cleanup will happen (and attributes can be ignored by the implementation).
#ifdef __STDC_CLEANUP__
#define my_cleanup(func) stdc_cleanup(func)
#else
#define my_cleanup(func) __attribute__((cleanup(func)))
#endif
i.e. it would require that it at least goes in the same places as an attribute.So I guess it needs someone to take that and update it, also to pull up a full list of current Linux software which is using this feature (which as I say these days is a surprising amount).
#define frr_with_privs(privs) \
for (struct zebra_privs_t *_once = NULL, \
*_privs __attribute__( \
(unused, cleanup(_zprivs_lower))) = \
_zprivs_raise(privs, __func__); \
_once == NULL; _once = (void *)1)
This gives us a block construct that guarantees elevated privileges are dropped when the block is done: frr_with_privs(privs) {
... whatever ...
break; /* exit block, drop privileges */
return; /* return, drop privileges */
}https://github.com/libguestfs/nbdkit/blob/e58d28d65bfea3af36...
You end up with code like this:
https://github.com/libguestfs/nbdkit/blob/e58d28d65bfea3af36...
It's so useful to be able to be sure the lock is released on all return paths. Also because it's scope-level you can scope your locks tightly to where they are needed.
It's used by a lot of current software in Linux, notably systemd and glib2. It solves a major headache with C error handling elegantly. Most compilers already support it internally (since it's required by C++). It has predictable effects, and no impact on performance when not used. It cannot be implemented without help from the compiler.