6 ms·
Really, don't do this, it's a portability and safety nightmare (aside from C not being memory safe already). C programmers are better off with either of these
by rwmj 2y ago
Really, don't do this, it's a portability and safety nightmare (aside from C not being memory safe already).
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.html 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.)
- MrBuddyCasino 2y ago> __attribute__((cleanup)) Interesting. I'm not very proficient in C, this looks like some sort of finalizers for local variables?
- rwmj 2y agoCorrect. You can use it in a simple way to free memory, but we've also used it to create scoped locks[1]. This being C, it's not without its problems. You cannot use it for values that you want to return from the function (as you don't want those to be freed), so any such variables cannot be automatically cleaned up on error paths either. Also there's no automated checking (it's not Rust!) Note it's {...} scoped, not function scoped, which makes it more useful than Golang's defer. [1] https://gitlab.com/nbdkit/nbdkit/-/blob/8b36e5a2ea331eed2a735627facd05d70a183284/common/utils/cleanup.h#L59 https://gitlab.com/nbdkit/nbdkit/-/blob/8b36e5a2ea331eed2a73...
- fpoling 2y agoWhile Go rules effectively prevents usage of defer in loops, it is useful occasionally to write: if complex_nested_condition { defer cleanup() }
- TheDong 2y agoEven with scope-based defer, you can accomplish conditional defers easily enough. In a sane language, where conditions are expressions, you could just do: defer if complex_nested_condition { cleanup() } else { noop() } In Go, you could do: defer func(run bool) { if !run { return } }(condition) Which admittedly wastes stack space with a noop function in the false case, but whatever. I feel like the number of times I've needed conditional defers is almost zero, while the number of times I've had to make a new function to ensure scoping is correct is huge. Of especial note, 'mu.Lock(), defer mu.Unlock()' not being scope-based is the largest source of deadlocks in code. People don't use 'defer' because the scoping rules are wrong, code panics before the manual unlock call, and then the program is deadlocked forever.
- NekkoDroid 2y ago> You cannot use it for values that you want to return from the function I would say this is only half true. With some macro magic you can actually also return the values :) https://github.com/systemd/systemd/blob/0201114bb7f347015ed4d85256637921dc304fe0/src/fundamental/macro-fundamental.h#L413-L422 https://github.com/systemd/systemd/blob/0201114bb7f347015ed4... To be fair though, you probably meant without any such shenanigans.
- devit 2y agoYou can just zero out the pointer variable with __attribute__((cleanup)) before returning its value (from a temporary copy of course).
- maccard 2y ago> 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. It’s odd that the suggestion for a feature lacking in C is to use a non standard but well used supported path. c’s main selling point (IMO) is that it _is_ a standard, and relying on compiler vendor extensions kind of defeats the purpose of that.
- milesrout 2y agoC's main selling point is not standardisation. It was widely used before standardisation and only standardised because it was useful.
- rwmj 2y agoIt's so widely used by OS software that you're likely using already, that it's unlikely to be removed and much more likely to be standardized. This is in fact how standardization ought to work - standardize the proven best practices.
- maccard 2y agoI agree. But if we follow that logic then any compiler specific feature of either or clang is fair game, even if it’s not standard. MSVC doesn’t support it when compiling in C mode, for example.
- lelanthran 2y ago> But if we follow that logic then any compiler specific feature of either or clang is fair game, even if it’s not standard. Well, yeah... How do you think Annex K got in?
- dietr1ch 2y ago> relying on compiler vendor extensions kind of defeats the purpose of that. Let's be honest, how many compilers are available, and how many of those would you actually use? The answer isn't more than 4 and the 2 compilers you are most likely to use among those already support this and probably won't stop supporting without a good alternative. I like standardisation, but you have to be realistic when it helps you without a large real cost other than fighting your ideals for getting this into the standard first.
- deleted 2y ago[deleted]
- masklinn 2y ago> we hope will be added to the C spec one day defer seems to be making significant progress (having a passionate and motivated advocate in Meneide, and a full TS)
- rwmj 2y agoLast time I looked this was (golang-like) function scoped, not { } scoped, which means it's a bad idea. My feedback was the committee should simply standardize the existing attribute / behaviour, as that is widely used already. (EDIT: I'm wrong, see reply)
- masklinn 2y ago> Last time I looked this was (golang-like) function scoped, not { } scoped, which means it's a bad idea. Might have been the previous attempt from years ago, because being block scoped (unlike go) literally has its own section in https://thephd.dev/c2y-the-defer-technical-specification-its-time-go-go-go#scope-based https://thephd.dev/c2y-the-defer-technical-specification-its...
- EPWN3D 2y agodefer is nice, but I really want the cleanup attribute since it could in theory by applied to the return type of a function. In other words you could have malloc return a pointer with the cleanup attribute that automatically frees it at end of scope if it's non-NULL. (And if you want to persist the pointer just assign to a different variable and zero out the one malloc gave you.)
- masklinn 2y ago> In other words you could have malloc return a pointer with the cleanup attribute that automatically frees it at end of scope if it's non-NULL. That is not, as far as I know, how __attribute__((cleanup)) works. It just invokes the callback when the value goes out of scope. So you can't have malloc return an implicitly cleanup'd pointer unless malloc is a macro, in which case you can do the same with a defer block.
- legohead 2y agoMy C programs never consumed gigs of memory. So I (like many others I assume) made a memory manager and never freed anything. You'd ask it for memory and it kept a list of various sizes it allocated and returned what you needed to be re-used. Freeing and allocating is slow, and error prone, so just avoid it!
- KerrAvon 2y agoA venerable and completely reasonable approach for resource-constrained environments and/or programs with very small memory requirements (kilobytes).
- adamrezich 2y agoWhat makes you think that this approach is only useful for resource-constrained circumstances?
- rwmj 2y agoYeah it's reasonable. Unfortunately if you do it, and you run tools like Coverity, it'll produces reams of complaints about how you're leaking memory :-( There was one project which was genuinely a short-lived program that never needed to free memory, but in the end I gave in and added free() statements everywhere. Otherwise we could never have got it into RHEL.
- adamrezich 2y agoWhy not just put all the free()s at the end of main() behind an #ifdef DEBUG or something?
- legohead 2y agoWhat are your issues with the memory requirements being small? One of the programs was a MUD that consumed a couple hundred megabytes, and I never had issues with it. I mentioned gigabytes because of how mine specifically worked. It allocated chunks in powers of 2, so there was some % of memory that wasn't being used. For instance, If you only need 20 bytes for a string, you got back a pointer for a chunk of 32 bytes. Being just a game, and side project, I never gave it much thought, so I'm curious to hear your input.
- pajko 2y agoThere's a complete implementation available at https://github.com/Snaipe/libcsptr https://github.com/Snaipe/libcsptr
- cryptonector 2y agoSure, but TFA is a very clever hack -- disgusting, yes, impractical and non-portable, true, subject to sad limitations -of course-, but genius and entertaining. The "tl;dr" summary is that `free_on_exit()` replaces the caller's return address with a trampoline that calls a `do_exit()` function that frees all the memory marked to be freed "on exit", and it uses its own stack of cleanup handler closures so-to-speak. When returning all the allocations are freed and then the original return address is returned to.
- forrestthewoods 2y ago> Use __attribute__((cleanup)). It's available in GCC and Clang Really, don’t do this, it’s a portability nightmare. If you’re going to write C stick to code that easy to run under MSVC.
- rossy 2y agoRunning under MSVC is overrated and more trouble than its worth. Clang and mingw-w64 GCC work just fine for targeting Windows.
- fc417fc802 2y agoRunning under MSVC implies I'm building on Windows. In reality I'm cross compiling with Clang.
- forrestthewoods 2y agoIt’s a shame that glibc is so badly designed that cross-compiling for Linux is only vaguely possible because Andrew Kelley moved mountains via Zig.
- forrestthewoods 2y agoDisagree. It’s fine and trivially easy. It’s only hard for Linux people who build software for Linux first and only. Then have a shocked pikachu face when they need to run in a different environment. mingw is radically more problematic than MSVC. Don’t use mingw.
- acbits 2y agohttps://github.com/acbits/reftrack-plugin https://github.com/acbits/reftrack-plugin Hey, I wrote this GCC plugin to automate reference counting in C. Any bug reports welcome.
- bch 2y ago> ... plus [reference counting] interacts badly with how modern CPUs work. Can you expand on this?
- sweetjuly 2y agoI'm not sure what the reference to "modern CPUs" is, but a common complaint is that most reasonable reference counting implementations suffer very badly under contention. Specifically, if an object's reference count is contended (an object is being accessed/its pointer copied by many threads), it's possible that incrementing and then decrementing the reference count can take several hundred cycles due to either cache lines ping-ponging between cores or relatively expensive remote atomics (some Arm CPUs, I believe, allow the memory system to execute atomic operations in caches themselves to try and cope with contention/avoid moving cache lines back and forth by simply leaving them in a shared cache). In reality, your milage will heavily vary. If you don't have contention (you don't commonly share objects across multiple threads concurrently), it's likely that reference counting will perform very well. Whether this is the common case really depends on the kinds of software you write.
- magicalhippo 2y agoSame object doesn't have to be contended if the reference count is sharing cache line with another reference count. I've seen this happen in cases arrays of objects are allocated, ie one per thread, and then handed to a thread pool to work on. Even if heap allocated, if the object is just a reference counter and a few pointers, the memory allocator can fit several of them next to each other causing them to share cache lines, which causes the performance issues with atomic operations. Depends on implementation of things of course, but can be a pitfall.