by seansh on 3/17/25, 11:29 AM with 164 comments
by rwmj on 3/17/25, 12:14 PM
C programmers are better off with either of these two techniques:
* Use __attribute__((cleanup)). It's available in GCC and Clang, and we hope will be added to the C spec one day. This is widely used by open source software, eg. in systemd.
* Use a pool allocator like Samba's talloc (https://talloc.samba.org/talloc/doc/html/libtalloc__tutorial...) or Apache's APR.
(I didn't include using reference counting, since although that is also widely used, I've seen it cause so many bugs, plus it interacts badly with how modern CPUs work.)
by flohofwoe on 3/17/25, 2:17 PM
Objects often come in batches of the same type and similar maximum lifetime, so let's make use of that.
Instead of tracking the individual lifetimes of thousands of objects it is often possible to group thousands of objects into just a handful of lifetime buckets.
Then use one arena allocator per lifetime bucket, and at the end of the 'bucket lifetime' discard the entire arena with all items in it (which of course assumes that there are no destructors to be called).
And suddenly you reduced a tricky problem (manually keeping track of thousands of lifetimes) to a trivial problem (manually keeping track of only a handful lifetimes).
And for the doubters: Zig demonstrates quite nicely that this approach works well also for big code bases, at least when the stdlib is built around that idea.
by PortiaBerries on 3/18/25, 12:26 AM
by scoopr on 3/17/25, 12:41 PM
[0] https://thephd.dev/c2y-the-defer-technical-specification-its...
by casenmgreen on 3/17/25, 10:29 PM
It is first argument to all functions.
If no errors, function proceeds - if error, function instead simply immediately returns.
When allocating resource, resource is recorded in a btree in the state.
When in a function an error occurs, error is recorded in state; after this point no code executes, because all code runs only if no errors.
At end of function is boilerplate error, which is added to error state if an error has occurred. So for example if we try to open file and out of disk, we first get error "fopen failed no disk", then second error "opening file failed", and then all parent functions in the current call stack will submit their errors, and you get a call stack.
Program then proceeds to exit(), and immediately before exit frees all resources (and in correct order) as recorded in btree, and prints error stack.
by Tewboo on 3/17/25, 12:59 PM
by Dwedit on 3/17/25, 2:17 PM
It would also need to be a function that will truly be implemented as one following the ABI, which usually happens when the function is exported. Often times, internal functions won't follow the platform ABI exactly.
Just changing the compiler version is probably enough to break anything like this.
Save the return address highjacking stuff for assembly code.
---
Meanwhile, I personally have written C code that does mess with the stack pointer. It's GBA homebrew, so the program won't quit or finish execution, and resetting the stack pointer has the effect of giving you a little more stack memory.
by abcd_f on 3/17/25, 12:41 PM
by queuebert on 3/17/25, 1:40 PM
by p0w3n3d on 3/17/25, 1:15 PM
I see undefined behaviours.
they walk
they talk
they [0?W0OF??0?r??reeBSD
they don't know they're undefined.
by pjdesno on 3/17/25, 12:10 PM
by qwertox on 3/17/25, 2:31 PM
by lionkor on 3/18/25, 9:44 AM
No, it does not. Smart pointers are useful to help model lifetimes and ownership, but the real killer feature is RAII. Add that to C (standardized) and you can make smart pointers, and any other memory management primitive you need. Smart pointers are not a solution, they are one of many tools enabled by RAII.
by PeterWhittaker on 3/17/25, 2:34 PM
2) I recently discovered the implementation of free_on_exit won't work if called directly from main if gcc aligns the stack. In this case, main adds padding between the saved eip and the saved ebp, (example). I think this can be fixed some tweaking, and will update this article when it is fixed.
I do not believe the article was updated, suggesting that the "tweaking" was far more complex than the author expected...
...which doesn't surprise me, because the overall tone is one of a clever but far-less-experienced-than-they-think programmer having what they think is a flash of insight and realizing thereby they can solve simply a problem that has plagued the industry and community for decades.
by jay-barronville on 3/18/25, 2:52 AM
by kazinator on 3/18/25, 5:06 AM
by mac3n on 3/17/25, 4:16 PM
the way i do this in C looks like
initialize all resource pointers to NULL;
attempt all allocations;
if all pointers are non-NULL, do the thing (typically calling another routine)
free all non-NULL pointers
realloc(ptr, 0) nicely handles allocations and possible-NULL deallocationsby qalmakka on 3/17/25, 1:01 PM
by whatsakandr on 3/17/25, 2:01 PM
by nanolith on 3/18/25, 12:27 AM
by feverzsj on 3/17/25, 12:49 PM
by afarah1 on 3/17/25, 1:04 PM
I haven't used it personally yet, but it addresses the same issue with a different approach, also related to stack-like lifetimes.
I've used simple reference counting before, also somewhat relevant in this context, and which skeeto also has a nice post about: https://nullprogram.com/blog/2015/02/17/
by adrianN on 3/17/25, 12:04 PM
by anacrolix on 3/17/25, 10:18 PM
by devit on 3/18/25, 3:58 AM
Use C++ or __attribute__((cleanup)) instead.
by Joker_vD on 3/17/25, 12:50 PM