-
Notifications
You must be signed in to change notification settings - Fork 0
sueszli/defer.c
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
▄▄ ▄▄▄▄ ██ ██▀▀▀ ▄███▄██ ▄████▄ ███████ ▄████▄ ██▄████ ▄█████▄ ██▀ ▀██ ██▄▄▄▄██ ██ ██▄▄▄▄██ ██▀ ██▀ ▀ ██ ██ ██▀▀▀▀▀▀ ██ ██▀▀▀▀▀▀ ██ ██ ▀██▄▄███ ▀██▄▄▄▄█ ██ ▀██▄▄▄▄█ ██ ██ ▀██▄▄▄▄█ ▀▀▀ ▀▀ ▀▀▀▀▀ ▀▀ ▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀ a tiny, portable, header-only C library for a scope-based cleanup (RAII-like semantics). works both with gcc and clang (with `BlocksRuntime` on linux). ``` $ tail -n +6 src/main.c int main(void) { char *mem = malloc(100); defer({ free(mem); printf("mem freed\n"); }); printf("mem allocated\n"); return EXIT_SUCCESS; } $ make run mem allocated mem freed $ make docker-gcc-test 100% tests passed, 0 tests failed out of 1 $ make docker-clang-test 100% tests passed, 0 tests failed out of 1 ``` # limitations (1) you can only have one defer block per line, because the `UNIQUE_NAME` macro uses `__LINE__` for name generation. ``` BAD: defer({ cleanup_a(); }); defer({ cleanup_b(); }); GOOD: defer({ cleanup_a(); }); defer({ cleanup_b(); }); ``` (2) clang requires the `__block` qualifier for mutable variables in defer blocks. ``` GOOD: __block int counter = 0; // required for clang defer({ counter++; }); ``` (3) clang can't capture arrays in defer blocks, even with `__block`. use pointers instead. ``` BAD: int arr[3] = {1, 2, 3}; defer({ arr[0] = 0; }); GOOD: int arr[3] = {1, 2, 3}; int *p = arr; // required for clang defer({ p[0] = 0; }); ``` (4) the name `ptr` is reserved. (5) comma operators are interpreted as macros. use semicolons instead. ``` BAD: defer({ a = 1, b = 2; }); // ERROR: comma seen as separator GOOD: defer({ a = 1; b = 2; }); // OK: semicolon separates statements ``` (6) `setjmp`/`longjmp` is not supported. ``` BAD: int *data = malloc(100); defer({ free(data); }); if (setjmp(buf)) { return; // jumped here, defer didn't execute, data is leaked } ``` (7) signal handlers (signals, abort, _exit) are not supported. (8) `goto` statements behave counterintuitively. jumping OVER a defer block means it never gets executes. jumping OUT OF a defer scope will execute the defer (as expected). ``` BAD: goto skip; defer({ cleanup(); }); // skipped, never executes skip: ``` (9) defer blocks are executed in LIFO order (matching Go's defer behavior). defer executes AFTER return value is computed but BEFORE function exits. ``` GOOD: defer({ printf("1"); }); defer({ printf("2"); }); defer({ printf("3"); }); // prints: "321" ``` (10) defer scopes are created by curly braces `{}`. each scope has its own defer blocks. this includes function bodies, loops, and conditional blocks. (11) variables captured by defer block must outlive the defer's execution. # references interesting reads and alternative implementations, each with their own caveats: - https://thephd.dev/_vendor/future_cxx/technical%20specification/C%20-%20defer/C%20-%20defer%20Technical%20Specification.pdf - https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/cleanup.h - https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2895.htm - https://thephd.dev/c2y-the-defer-technical-specification-its-time-go-go-go - https://fdiv.net/2015/10/08/emulating-defer-c-clang-or-gccblocks - https://gist.github.com/eloraiby/f64fcba0d489f0d31aa544d66cbfd7a6
About
portable scope-based cleanup