7 ms·
Show HN: A simple garbage collector for C
- tekknolagi 7y agoIs there a simple GC like this one that uses handles instead of C stack scanning to keep track of local references?
- shakna 7y agoThe Boehm GC seems to do both internally.
- swiley 7y agoI just wish C had something like defer in go, That would cover most cases.
- shakna 7y agoDefer can be emulated with the block extensions of Clang and GCC [0], though I'm not sure how much I'd like to see something like that in a codebase. [0] https://web.archive.org/web/20180426195701/http://fdiv.net/2015/10/08/emulating-defer-c-clang-or-gccblocks https://web.archive.org/web/20180426195701/http://fdiv.net/2...
- gnulinux 7y agoThat link seems to be broken? Nothing opens when I click it.
- saagarjha 7y agoWorks fine for me. It's using __attribute__((cleanup)) to call a function that calls the block you give it.
- shakna 7y agoIt's a short article, and worth reading if you can work out why the Wayback Machine isn't working for you. The magic is basically: static inline void defer_cleanup(void (^*b)(void)) { (*b)(); } #define defer_merge(a,b) a##b #define defer_varname(a) defer_merge(defer_scopevar_, a) #define defer __attribute__((cleanup(defer_cleanup))) void (^defer_varname(__COUNTER__))(void) = Which let's you do: FILE *a = fopen ("a.txt", "r"); if (!a) return EXIT_FAILURE; defer { fclose (a); }; It uses cleanup and blocks, both of which are extensions. There may also be some strangeness with the way blocks hold memory. (Block references become const copies).
- Twirrim 7y agohttp://fdiv.net/2015/10/08/emulating-defer-c-clang-or-gccblocks http://fdiv.net/2015/10/08/emulating-defer-c-clang-or-gccblo... The original link works.
- shakna 7y agoThe original link pops up with a HTTP basic authentication request for me, hence the use of archive.
- simias 7y agoThere's also __attribute__ ((__cleanup__(<destructor>))) which can be used to implement RAII in C.
- shakna 7y agoThat's exactly what this uses. It just hides it inside a syntax that is more similar to what Go uses.
- simias 7y agoAh, apologies, the link wasn't loading at the moment and I assumed it was something else. Regarding its use in the wild, I've encountered it exactly once, in the lastpass-cli (available on github). I personally wouldn't consider using it for one of my projects because it's too nonstandard, however I suppose that for a security-sensitive application it might reduce the likelihood of messing error handling and leaking things all over the place. That being said using non-standard features might also make it more likely for a contributor to misunderstand what the code does precisely and introduce a problem.
- cyphar 7y agoLXC uses it as well, and they have additional macros to "move" pointers and file descriptors to make sure you don't double-free or similar things.
- skolskoly 7y agoIf you're willing to use macros, this can do the trick. Return and break will preempt the expression, but continue should work fine. #define DEFER(EXPR) for(int _tmp=1; _tmp; _tmp=0,(EXPR)) Example: char * data = malloc(32); DEFER(free(data)) { // do stuff }
- jonas21 7y agoOh, that's pure evil/genius.
- BubRoss 7y agoThat is really clever, I don't know why I never thought about the for loop being a way to run an expression after a scope ends.
- adtac 7y agoPerhaps I'm misunderstanding this, but this doesn't solve the problem at all -- returning within the block statement wouldn't call the deferred expression, which is entirely the point of a defer statement, no? If control is guaranteed to reach the end of the block statement, of course; but requiring such a constraint would make this defer very handicapped. You have tons of function exit points and you most likely want to free memory at every single one.
- seventh-chord 7y agoRegarding having tons of exit points, I find it actually adds no more cognitive load to use only a single return per function, compared to having multiple returns and keeping resource-freeing up-to-date for each. Most of the challenge in writing code with a single return seems to come from the fact that I'm not used to doing it. Of course, sometimes it leads to heavily nested control flow, although I'm not sure yet whether or not I mind that. It does certainly simplify reasoning about control flow though, especially in large functions. That all being said, I'd still rather just have defer.
- simias 7y ago
- dgellow 7y agoI know that's not directly what you're asking for, but I found the other day that "C++ Core Guidelines" have a helper library "GSL_util" that provide something similar to Go's defer. The implementation is very simple, it's just using a class for its destructor: template <class F> class final_act { public: explicit final_act(F f) noexcept : f_(std::move(f)), invoke_(true) {} final_act(final_act&& other) noexcept : f_(std::move(other.f_)), invoke_(other.invoke_) { other.invoke_ = false; } final_act(const final_act&) = delete; final_act& operator=(const final_act&) = delete; ~final_act() noexcept { if (invoke_) f_(); } private: F f_; bool invoke_; }; Source: https://github.com/microsoft/GSL/blob/ebe7ebfd855a95eb93783164ffb342dbd85cbc27/include/gsl/gsl_util#L53-L89 https://github.com/microsoft/GSL/blob/ebe7ebfd855a95eb937831...
- saagarjha 7y agoOf course, in C++ the need for such constructs is reduced due to RAII and scoping running destructors automatically. This is useful when you need to interact with C libraries that you don’t want to wrap in C++ (and when I was doing something like this in the past, I rolled my own as well).
- dgellow 7y ago> This is useful when you need to interact with C libraries that you don’t want to wrap in C++ Yep! I really like the defer pattern for those situations :)
- cshenton 7y agoCheck out Zig if you’re looking for a C replacement with a go style defer. It also interops about as well with C as C does.
- kitd 7y agoOr D, which integrates with native C libs and has the concept of 'exit scope' for deferred code.
- swiley 7y agoI thought that depends on llvm though? Not to mention it doesn’t allow horizontal tabs which I dislike pretty intensely, different programming styles and environments work with different tab stops and it makes a lot of sense (IMO) to represent indentation with a single character.
- haecceity 7y agoThere’s boost scope exit but you have to use c++
- cesarb 7y agoIf you're willing to use GCC extensions (available also on clang, so should cover most operating systems, even Windows with clang-cl), there's __attribute__((__cleanup__(fn))), as used for instance by systemd: https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Variable...
- maccard 7y agoThis seems like a great place to plug C++ - Destructors are what you want. While I'm personally a fan of C++-as-a-whole, there's nothing stopping you using C-with-classes. You can "emulate" the defer statement with a Scope Guard[0], or if you're willing to use a macro, you can use the __COUNTER__ macro as an "anonymous" variable. [0] https://github.com/ricab/scope_guard https://github.com/ricab/scope_guard
- cyphar 7y agoThe primary use-case of defer (cleaning up resources) can be done with __attribute__((__cleanup__(<destructor>))). This effectively allows you to do RAII in C, though it does require some careful thought when copying variables (you need to "move" them a-la Rust). But the really classic way of doing "defer" in C is to do what Linux does: int foo(void) { char *ptr = malloc(13); int error = 0; error = bar(); if (error < 0) goto out_free; // do something else out_free: free(ptr); return error; } Once you get used to it, it feels very natural to write and understand.
- kickscondor 7y agoCool! Thanks for this - and the link to orangeduck’s work (which further points to Cello.)
- davnicwil 7y agoJust out of interest for people not familiar with C, latest goings on there etc, what's the usecase where you'd want a garbage collector in C? My (very surface level) understanding was always the trade off for the increased manual effort of using C - manual memory management being one example - was that you could tailor your solution exactly to your usecase for increased performance / lower resource use. If you're going for a garbage collector, why not also benefit from some of the increased language power/features of a higher level language?
- devinalvaro 7y agoAs the author mentioned in the README, it's going to be used for the LISP interpreter he/she is writing from scratch.
- antoinealb 7y agoThe usecase for this project (according to the README) is: > The original motivation for gc is my desire to write my own LISP in C, entirely from scratch - and that required garbage collection. Another reason would be in platforms where you cannot run the language of your choice (because lack of implementations of JVM / Python / whatever), although those would maybe not allow malloc either.
- tjpnz 7y agoI could imagine something like this being applicable towards large legacy codebases with a history of memory leaks and segfaults. With legacy codebases you're often only able to tread water and won't be in a position to track everything to it's root cause.
- pdpi 7y agoAdding a GC to such a code base is a terribly complex endeavour and would only make things even worse though.
- pdpi 7y agoThink about it the other way around — Java, Python, JavaScript all have garbage collectors. What language are those GCs written in? How does the runtime keep track of the objects in those languages, and how is the lifecycle for that tracking managed? "Writing a GC in C" (or equivalent language) is not so much a strange thing as it is an inevitability.
- touchpadder 7y agoCan it help with compiling Typescript to WASM? i.e. in https://github.com/AssemblyScript/assemblyscript https://github.com/AssemblyScript/assemblyscript
- techntoke 7y agoWhat would give you that impression?
- maxgraey 7y agoAssemblyScript already has tiny hybrid garbage collector (PureRC) which use deferred ARC and GC only for cyclic references: https://github.com/dcodeIO/purerc/blob/master/papers/Bacon03Pure.pdf https://github.com/dcodeIO/purerc/blob/master/papers/Bacon03...
- Rochus 7y agoHow about efficiency? Looks like the collector has to scan through all allocated memory and the whole stack to check whether there could be a pointer. And if any data looks like such a pointer (even by coincidence) the corresponding memory cannot be collected. Anyway: isn't this just the same concept as the Boehm conservative GC? What's the difference/improvement?
- hu3 7y agoSecond paragraph in README: > The focus of gc is to provide a conceptually clean implementation of a mark-and-sweep GC, without delving into the depths of architecture-specific optimization (see e.g. the Boehm GC for such an undertaking). It should be particularly suitable for learning purposes and is open for all kinds of optimization (PRs welcome!).
- jhammond1 7y agoI was hoping for a link to the exit() manpage.
- mrobot 7y agoI am actually doing this exact same thing right now and will reference this for comparison once i am done with the first version of mine. I am doing stop-and-copy though.
- deleted 7y ago[deleted]