16 ms·
Giving C a superpower: custom header file (safe_c.h)
- immibis 11mo agoThis feels AI-generated.
- JonChesterfield 11mo agoGuarding builtin_expect with __GNUC__ instead of has_builtin might be what you find in elderly codebases that were fed into an llm
- immibis 11mo agoNo, that's pretty normal. It's the English writing that feels AI.
- krapht 11mo agoC++: "look at what others must do to mimic a fraction of my power" This is cute, but also I'm baffled as to why you would want to use macros to emulate c++. Nothing is stopping you from writing c-like c++ if that's what you like style wise.
- Lerc 11mo agoPerhaps but a project using this stops you from writing any old C++ in your C. Writing C++ in a C style has no such protection. It's choosing which features are allowed in.
- Qwuke 11mo agoIt's interesting to me to see how easily you can reach a much safer C without adding _everything_ from C++ as a toy project. I really enjoyed the read! Though yes, you should probably just write C-like C++ at that point, and the result sum types used made me chuckle in that regard because they were added with C++17. This person REALLY wants modern CPP features..
- loup-vaillant 11mo ago> I'm baffled as to why you would want to use macros to emulate c++. I like the power of destructors (auto cleanup) and templates (generic containers). But I also want a language that I can parse. Like, at all. C is pretty easy to parse. Quite a few annoying corner cases, some context sensitive stuff, but still pretty workable. C++ on the other hand? It’s mostly pick a frontend or the highway.
- CyberDildonics 11mo agoThere was a language called clay that was C compatible but had move semantics, destructors, templates and operator overloading.
- _vqpz 11mo ago>Nothing is stopping you from writing c-like c++ if that's what you like style wise. You'll just have to get used to the C++ community screaming at you that it's the wrong way to write C++ and that you should just use Go or Zig instead
- deleted 11mo ago[deleted]
- sesm 11mo agoEmbedded CPU vendors not shipping C++ compilers is what usually stops people.
- kjs3 11mo agoYup. And I like the implication that Rust is 'cross platform', when it's 'tier 1' support consists of 2 architectures (x86 & arm64). I guess we're converging on a world where those 2 + riscv are all that matter to most people, but it's not yet a world where they are all that matter to all people. [1] https://doc.rust-lang.org/beta/rustc/platform-support.html https://doc.rust-lang.org/beta/rustc/platform-support.html
- dontlaugh 11mo agoThe converging is happening in practice. I’m using a keyboard running RMK for firmware, written in Rust.
- okanat 11mo agoYou are misunderstanding what Tiers are. Embedded architectures cannot be Tier 1 which requires running Rust compiler compiled on the architecture itself and testing stuff on with it. Only full desktop systems will be Tier 1, because those are the systems that you can run Rustc and all the nice desktop environments. However most of the embedded world uses ARM chips and they are Tier 2 like thumbv6m and thumbv7em (there are still odd ones like 8051 or AVR or m68k, many of them lack a good C++ compiler already). They are guaranteed to be built and at the release time the tests still run for them.
- kjs3 11mo agoI understand your tiers just fine. You are misunderstanding what "cross platform" means. Or rather, you're trying to redefine it to mean "what Rust supports, in the way we want to support it, on the few architectures we care about, because in our view nothing else of value exists". However most of the embedded world uses ARM chips My point exactly.
- 11mo ago
- dboon 11mo agoNo name mangling by default, far simpler toolchain, no dependence on libstdc++, compiles faster, usable with TCC/chibicc (i.e. much more amenable to custom tooling, be it at the level of a lexer, parser, or full compiler). C’s simplicity can be frustrating, but it’s an extremely hackable language thanks to that simplicity. Once you opt in to C++, even nominally, you lose that.
- phs2501 11mo agoI highly doubt (and some quick checks seem to verify that) any of the tiny CC implementations will support the cleanup extension that most of this post's magic hinges upon. (Agree on your other points for what it's worth.)
- fuhsnn 11mo agoTinyCC supports cleanup[1], onramp[2] supports C2y defer (which is a superset), slimcc[3] supports both. [1] https://godbolt.org/z/hvj9vcncG https://godbolt.org/z/hvj9vcncG [2] https://github.com/ludocode/onramp https://github.com/ludocode/onramp [3] https://github.com/fuhsnn/slimcc https://github.com/fuhsnn/slimcc
- cryptonector 11mo agoIf you have a pile of C, switching to using C++ is not easy.
- miroljub 11mo agoNice toy. It works until it stops working. An experienced C developer would quickly find a bunch of corner cases where this just doesn't work. Given how simple examples in this blog post are, I ask myself, why don't we already have something like that as a part of the standard instead of a bunch of one-off personal, bug-ridden implementations?
- Borg3 11mo agoYeah, kids like to waste time to make C more safe or bring C++ features. If you need them, use C++ or different language. Those examples make code look ugly and you are right, the corner cases. If you need to cleanup stuff on early return paths, use goto.. Its nothing wrong with it, jump to end when you do all the cleanup and return. Temporary buffers? if they arent big, dont be afraid to use static char buf[64]; No need to waste time for malloc() and free. They are big? preallocate early and reallocate or work on chunk sizes. Simple and effective.
- lukan 11mo agoCan you share such a corner case?
- Borg3 11mo agoNo, because I did NOT do serious analisis of this. Nor I care, ask upper commenter.. C have some corner case and undefined behaviours and this stuff will make it worse IMO.
- kstrauser 11mo agoGod forbid we should make it easier to maintain the existing enormous C code base we’re saddled with, or give devs new optional ways to avoid specific footguns.
- mightyham 11mo agoGoofy platform specific cleanup and smart pointer macros published in a brand new library would almost certainly not fly in almost any "existing enormous C code base". Also the industry has had a "new optional ways to avoid specific footguns" for decades, it's called using a memory safe language with a C ffi.
- rurban 11mo agoJust don't mix that up with the real safec.h header from safeclib: https://github.com/rurban/safeclib/tree/master/include https://github.com/rurban/safeclib/tree/master/include
- debugnik 11mo agoHow can anyone be this interested in maintaining an annex k implementation when it's widely regarded as a design failure, specially the global constraint handler. There's a reason why most C toolchains don't support it. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm
- quotemstr 11mo agoFWIW, it's heavily used inside Microsoft and is actually pretty nice when combined with all the static analysis tools that are mandatory parts of the dev cycle.
- debugnik 11mo agoAFAIK Microsoft's API is still a previous iteration not compliant with the standard annex K.
- rurban 11mo ago## Microsoft Windows/MINGW_HAS_SECURE_API * `fopen_s`, `freopen_s` deviate in the API: restrict is missing. * `strtok_s`, `wcstok_s`,`vsnprintf_s` miss the dmax argument. * `vsnprintf_s` adds a maxarg argument. * `vswprintf` adds a maxarg argument on w32. (with `__STRICT_ANSI__` undefined) * no `strnlen` on mingw32. * no `errno_t` return type for `qsort_s`, only `void`. * reversed argument order for `localtime_s` and `gmtime_s`. * older mingw versions have `wchar.h` with only 2 functions: `wcscmp`, `wcslen` * no `RSIZE_MAX` * `memmove_s` does not clear dest with ERANGE when `count > dmax` and EINVAL when src is a NULL pointer. * `vsprintf_s`, `sprintf_s` return `-1` on all errors, not just encoding errors. (Wrong standard) * With `wcsrtombs` (used by `wcsrtomb_s`) the `retval` result includes the terminating zero, i.e. the result is `+1` from the spec. `getenv_s` returns in len the size of the env buffer, not the len, as described in the standard (https://en.cppreference.com/w/c/program/getenv https://en.cppreference.com/w/c/program/getenv). The Microsoft size is len + 1. Their usage example is also wrong: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getenv-s-wgetenv-s?view=msvc-170 https://learn.microsoft.com/en-us/cpp/c-runtime-library/refe...
- keyle 11mo agoI don't understand this passion for turning C into what it's not... Just don't use C for sending astronauts in space. Simple. C wasn't designed to be safe, it was designed so you don't have to write in assembly. Just a quick look through this and it just shows one thing: someone else's walled garden of hell.
- fransje26 11mo ago> Just don't use C for sending astronauts in space. Simple. Last time I checked, even SpaceX uses C to send astronauts to space...
- pkhuong 11mo ago> Just don't use C for sending astronauts in space But do use C to control nuclear reactors https://list.cea.fr/en/page/frama-c/ https://list.cea.fr/en/page/frama-c/ It's a lot easier to catch errors of omission in C than it is to catch unintended implicit behavior in C++.
- debugnik 11mo agoI consider code written in Frama-C as a verifiable C dialect, like SPARK is to Ada, rather than C proper. I find it funny how standard C is an undefined-behaviour minefield with few redeeming qualities, but it gets some of the best formal verification tools around.
- 1718627440 11mo agoThe popular C compilers include a static analyzer and a runtime sanitizer. What features do you consider proper C? The C standard has always been about standardization of existing compilers, not about prescribing features.
- debugnik 11mo agoBy "static analysis" you mean unsound, best-effort analyzers which try to study code as-is, rarely allow extra annotations, and tolerate false negatives and even positives by design. While these are a huge improvement over no extra tooling, they don't compare to analyzers like Frama-C plugins, which demand further annotations/proofs if necessary to show code is free of UB, and you can provide further to show your code is not just safe, but correct. Assuming one doesn't ship rejected code, the latter is pretty much its own language with much stronger guarantees, much like SPARK is to Ada. I like sanitizers and other compiler-specific guarantees, they at least try to fill the gaps by giving proper semantics to UB. But the ones available for C are still insufficient, and some are very resource-heavy compared to just running safe code. I'm excited about Fil-C showing a path forward here.
- HexDecOctBin 11mo agoAny hopes that MSVC will add C23 support before 2040?
- nmeofthestate 11mo agoBit of a random question on an article about C.
- HexDecOctBin 11mo agoThe article clearly states that the code only works on GCC and Clang, which leaves MSVC. Not sure how the question was random.
- pjmlp 11mo agoThere are other C compilers.
- le-mark 11mo agoWill windows be relevant by 2040? I personally don’t think so.
- pjmlp 11mo agoGiven how C was seen in the past, before there was a change of heart to add C11/C17, minus atomics and aligned memory allocators (still between experimental or not going to happen), https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-and-c99 https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an... https://devblogs.microsoft.com/cppblog/c11-atomics-in-visual-studio-2022-version-17-5-preview-2/ https://devblogs.microsoft.com/cppblog/c11-atomics-in-visual... https://learn.microsoft.com/en-us/cpp/c-runtime-library/compatibility?view=msvc-170 https://learn.microsoft.com/en-us/cpp/c-runtime-library/comp... And the new guidelines regarding the use of unsafe languages at Microsoft, I wouldn't bet waiting that it will ever happen, even after 2040. https://azure.microsoft.com/en-us/blog/microsoft-azure-security-evolution-embrace-secure-multitenancy-confidential-compute-and-rust/ https://azure.microsoft.com/en-us/blog/microsoft-azure-secur... https://blogs.windows.com/windowsexperience/2024/11/19/windows-security-and-resiliency-protecting-your-business/ https://blogs.windows.com/windowsexperience/2024/11/19/windo...
- cachius 11mo agoA recent superpower was added by Fil aka the pizlonator who made C more Fil-C with FUGC, a garbage collector with minimal adjustments to existing code, turning it into a memory safe implementation of the C and C++ programming languages you already know and love. https://news.ycombinator.com/item?id=45133938 https://news.ycombinator.com/item?id=45133938 https://fil-c.org/ https://fil-c.org/
- deleted 11mo ago[deleted]
- 762236 11mo agoWhy would I want to run a garbage collector and deal with it's performance penalties?
- palata 11mo agoEasy: because in your specific use-case, it's worth trading some performance for the added safety.
- jerf 11mo agoBecause about 99% of the time the garbage collect is a negligible portion of your runtime at the benefit of a huge dollop of safety. People really need to stop acting like a garbage collector is some sort of cosmic horror that automatically takes you back to 1980s performance or something. The cases where they are unsuitable are a minority, and a rather small one at that. If you happen to live in that minority, great, but it'd be helpful if those of you in that minority would speak as if you are in the small minority and not propagate the crazy idea that garbage collection comes with massive "performance penalties" unconditionally. They come with conditions, and rather tight conditions nowadays.
- fuhsnn 11mo ago> C23 gave us [[cleanup]] attributes C23 didn't introduce it, it's still a GCC extension that needs to be spelled as [[gnu::cleanup()]] https://godbolt.org/z/Gsz9hs7TE https://godbolt.org/z/Gsz9hs7TE
- cassepipe 11mo agoIt is surprisingly hard to find information about it, do you have any ? From what I can guess it's a new syntax but it's the feature itself is still an extension ?
- SAI_Peregrinus 11mo agoThe `[[attribute]]` syntax is new, the builtin ones in C23 are `[[deprecated]]`, `[[fallthrough]]`, `[[maybe_unused]]`, `[[nodiscard]]`, `[[noreturn]]`, `[[reproducible]]`, and `[[unsequenced]]`.
- ksherlock 11mo ago[[ ]] attributes were added in C++11 and later C23. There are 7 standard(C32) attributes but GCC has hundreds of them. https://en.cppreference.com/w/c/language/attributes.html https://en.cppreference.com/w/c/language/attributes.html https://en.cppreference.com/w/cpp/language/attributes.html https://en.cppreference.com/w/cpp/language/attributes.html https://gcc.gnu.org/onlinedocs/gcc/Attributes.html https://gcc.gnu.org/onlinedocs/gcc/Attributes.html https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute...
- fuhsnn 11mo agoAlso clang's https://clang.llvm.org/docs/AttributeReference.html https://clang.llvm.org/docs/AttributeReference.html
- kccqzy 11mo agoThe feature itself is probably still __attribute__((cleanup(f))). That’s documented at https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute...
- hanstospace 11mo agoI checked Fil-C out and it looks great. How is this different from Fil-C? Apart from the obvious LLVM stuff
- woodruffw 11mo agoFil-C essentially lifts C onto a managed, garbage-collected runtime. This is a small header that adds some C++ features to C. (These features are still essentially unsafe: the unique pointer implementation still permits UAF, for example, because nothing prevents another thread from holding the pointer and failing to observe that it has been freed.)
- khaledh 11mo agoThe problem with macro-laden C is that your code becomes foreign and opaque to others. You're building a new mini-language layer on top of the base language that only your codebase uses. This has been my experience with many large C projects: I see tons of macros used all over the place and I have no idea what they do unless I hunt down and understand each one of them.
- naasking 11mo agoI think this can be fine if the header provides a clean abstraction with well-defined behaviour in C, effectively an EDSL. For an extreme example, it starts looking like a high-level language: https://www.libcello.org/ https://www.libcello.org/
- jnwatson 11mo agoLike the Linux kernel? Macros are simply a fact of life in any decent-sized C codebase. The Linux kernel has some good guidance to try to keep it from getting out of hand but it is just something you have to learn to deal with.
- foobarian 11mo agoObligatory link to Bourne Shell source code. https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/mac.h https://www.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh... Discussed here https://news.ycombinator.com/item?id=22191790 https://news.ycombinator.com/item?id=22191790
- woodruffw 11mo agoIntentionally or not, this post demonstrates one of the things that makes safer abstractions in C less desirable: the shared pointer implementation uses a POSIX mutex, which means it’s (1) not cross platform, and (2) pays the mutex overhead even in provably single-threaded contexts. In other words, it’s not a zero-cost abstraction. C++’s shared pointer has the same problem; Rust avoids it by having two types (Rc and Arc) that the developer can select from (and which the compiler will prevent you from using unsafely).
- kouteiheika 11mo ago> the shared pointer implementation uses a POSIX mutex [...] C++’s shared pointer has the same problem It doesn't. C++'s shared pointers use atomics, just like Rust's Arc does. There's no good reason (unless you have some very exotic requirements, into which I won't get into here) to implement shared pointers with mutexes. The implementation in the blog post here is just suboptimal. (But it's true that C++ doesn't have Rust's equivalent of Rc, which means that if you just need a reference counted pointer then using std::shared_ptr is not a zero cost abstraction.)
- woodruffw 11mo agoTo be clear, the “same problem” is that it’s not a zero-cost abstraction, not that it uses the same specific suboptimal approach as this blog post.
- kouteiheika 11mo agoI think that's an orthogonal issue. It's not that C++'s shared pointer is not a zero cost abstraction (it's as much a zero cost abstraction as in Rust), but that it only provides one type of a shared pointer. But I suppose we're wasting time on useless nitpicking. So, fair enough.
- woodruffw 11mo agoI think they’re one and the same: C++ doesn’t have program-level thread safety by construction, so primitives like shared pointers need to be defensive by default instead of letting the user pick the right properties for their use case. Edit: in other words C++ could provide an equivalent of Rc, but we’d see no end of people complaining when they shoot themselves in the foot with it. (This is what “zero cost abstraction” means: it doesn’t mean no cost, just that the abstraction’s cost is no greater than the semantically equivalent version written by the user. So both Arc and shared_ptr are zero-cost in a MT setting, but only Rust has a zero-cost abstraction in a single-threaded setting.)
- archargelod 11mo agoYou can get all of that and more with Nim[0]. Nim is a language that compiles to C. So it is similar in principle to the "safe_c.h". We get power and speed of C, but in a safe and convenient language. > It's finally, but for C Nim has `finally` and `defer` statement that runs code at the end of scope, even if you raise. > memory that automatically cleans itself up Nim has ARC[1]: "ARC is fully deterministic - the compiler automatically injects destructors when it deems that some variable is no longer needed. In this sense, it’s similar to C++ with its destructors (RAII)" > automated reference counting See above > a type-safe, auto-growing vector. Nim has sequences that are dynamically sized, type and bounds safe > zero-cost, non-owning views Nim has openarray, that is also "just a pointer and a length", unfortunately it's usage is limited to parameters. But there is also an experimental view types feature[2] > explicit, type-safe result Nim has `Option[T]`[3] in standard library > self-documenting contracts (requires and ensures) Nim's assert returns message on raise: `assert(foo > 0, "Foo must be positive")` > safe, bounds-checked operations Nim has bounds-checking enabled by default (can be disabled) > The UNLIKELY() macro tells the compiler which branches are cold, adding zero overhead in hot paths. Nim has likely / unlikely template[4] ------------------------------------------------------------ [0] https://nim-lang.org https://nim-lang.org [1] https://nim-lang.org/blog/2020/10/15/introduction-to-arc-orc-in-nim.html https://nim-lang.org/blog/2020/10/15/introduction-to-arc-orc... [2] https://nim-lang.org/docs/manual_experimental.html#view-types https://nim-lang.org/docs/manual_experimental.html#view-type... [3] https://nim-lang.org/docs/options.htm https://nim-lang.org/docs/options.htm [4] https://nim-lang.org/docs/system.html#likely.t%2Cbool https://nim-lang.org/docs/system.html#likely.t%2Cbool
- dboon 11mo agoNice, but if the intention is portability my experience has unfortunately been that you pretty much have to stick to C99. MSVC’s C compiler is rough, but pretty much necessary for actual cross platform. I have my own such header which has many, many things like the OP’s. As much as I would find it constantly useful, I don’t have a cleanup utility because of this. But if you can stay out of MSVC world, awesome! You can do so much with a few preprocessor blocks in a header
- Maxatar 11mo agoMSVC now supports C17.
- atiedebee 11mo agoDoes it support C99 with VLAs yet?
- hgs3 11mo agoIt supports C99 minus VLAs. Worth noting that only C99 mandates VLAs. They are optional in C11, C17, and C23.
- CyberDildonics 11mo agoMSVC is also made out of a dozen javascript processes which makes typing text need a beefy computer.
- electroly 11mo agoMSVC is a C++ compiler toolchain and it does not contain any JavaScript. You're thinking of VSCode, probably, but your comment was an off-topic rant either way.
- CyberDildonics 11mo agoMicrosoft visual studio the IDE (not vs code the electron program) has lots of javascript processes running in the background doing all sorts of things. Also my comment was a single sentence with a single fact so it can't be a rant.
- edwcross 11mo agoThe post mentions cgrep several times, but I don't see a link to the code. Is it available somewhere? Github has several repositories named cgrep, but the first results are written in other languages than C (Haskell, Python, Typescript, Java, etc).
- mgr86 11mo agoyes, it is frustrating. I also am not quite sure what he is referencing and would be interested in trying out cgrep for myself.
- kerkeslager 11mo agoI feel like there might be some value in this header file for some projects, but this is exactly the wrong use case. > In cgrep, parsing command-line options the old way is a breeding ground for CVEs and its bestiary. You have to remember to free the memory on every single exit path, difficult for the undisciplined. No, no, no. Command line options that will exist the entire lifetime of the program are the quintessential case for not ever calling free() on them because it's a waste of time. There is absolutely no reason to spend processor cycles to carefully call free() on a bunch of individual resources when the program is about to exit and the OS will reclaim the entire process memory in one go much faster than your program can. You're creating complexity and making your program slower and there is literally no upside: this isn't a tradeoff, it's a bad practice.
- krautburglar 11mo ago[dead]
- JonChesterfield 11mo agoshouldn't be individually allocated either, arenas for the win
- kerkeslager 11mo agoI think that a blanket should/shouldn't recommendation for arenas isn't right. Arenas are a tradeoff: Pros: preallocating one arena is likely faster than many smaller allocations. Cons: preallocation is most effective if you can accurately predict usage for the arena; if you can't, then you either overshoot and allocate more memory than you need, or undershoot and have to reallocate which might be less performant than just allocating as-needed. In short, if you're preallocating, I think decisions need to be made based on performance testing and the requirements of your program (is memory usage more important than speed?). If you aren't preallocating and just using arenas for to free in a group, then I'm going to say using an arena for stuff that is going to be freed by the OS at program exit is adding complexity for no benefit--it depends on your arena implementation (arenas aren't in the C standard to my knowledge). In general, I'd be erring on the side of simplicity here and not using arenas for this by default--I'd only consider adding arenas if performance testing shows the program spending a lot of time in individual allocations. So I don't think a blanket recommendation for arenas is a good idea here. EDIT: In case it's not obvious: note that I'm assuming that the reason you want to use an arena is to preallocate. If you're thinking that you're going to call free on the arena on program exit, that's just as pointless as calling free on a bunch of individual allocations on program exit. It MIGHT be faster, but doing pointless things faster is still not as good as not doing pointless things.
- purplesyringa 11mo agoThis feels like a misrepresentation of features that actually matter for memory safety. Automatically freeing locals and bounds checking is unquestionably good, but it's only the very beginning. The real problems start when you need to manage memory lifetimes across the whole program, not locally. Can you return `UniquePtr` from a function? Can you store a copy of `SharedPtr` somewhere without accidentally forgetting to increment the refcount? Who is responsible for managing the lifetimes of elements in intrusive linked lists? How do you know whether a method consumes a pointer argument or stores a copy to it somewhere? I appreciate trying to write safer software, but we've always told people `#define xfree(p) do { free(p); p = NULL; } while (0)` is a bad pattern, and this post really feels like more of the same thing.
- cryptonector 11mo ago> Can you return `UniquePtr` from a function? Yes: you can return structures by value in C (and also pass them by value). > Can you store a copy of `SharedPtr` somewhere without accidentally forgetting to increment the refcount? No, this you can't do.
- teo_zero 11mo ago> we've always told people `#define xfree(p) do { free(p); p = NULL; } while (0)` is a bad pattern Have we? Why?
- bad_username 11mo agoThis reminds me that Scott Meyers (IMO rightfully) considered the destructor as the single most important fearure in C++.
- JonChesterfield 11mo agoI too enjoy space leaks on recursion
- throwaway889900 11mo agoDoes the StringView/Span implementation here seem lacking? If the underlying data goes away, wouldn't the pointer now point to an invalid freed location, because it doesn't track the underlying data in any way?
- loeg 11mo agoThat's what std::string_view and std::span are, though. They're views / borrows over the underlying data, minus the kind of protections something like Rust has about lifetimes of the underlying data vs the borrow. The benefit of these types is that they're a pair of pointer+size, instead of just a bare pointer without a known size.
- throwaway889900 11mo agoTrue, I guess I was expecting if you were reimplementing these with additional protections, why not add the protection using the custom pointer reimplementations to ensure no use after frees. Seems like a missed opportunity.
- loeg 11mo agoI don’t think there is a way to do this generally without GC, and that leads to significant pitfalls anyway (eg in Java you can accidentally end up holding multi-GB strings in memory because a few references to short substrings exist).
- scottlamb 11mo agoI think because it's called `safe_c.h` and is "designed to give C some safety and convenience features from ... Rust", and says "The Memory Management Beast: Slain", you expected to see some level of memory safety, Rust's headline selling point. I did too when I opened the article. But it doesn't at all. Those phrases are all clickbait and slop. In fact I don't see anything here from Rust that isn't also in C++. They talk about Result and say "Inspired by Rust, Result forces you to handle errors explicitly by returning a type that is either a success value or an error value", but actually, unlike Rust, nothing enforces that you don't just incorrectly use value without checking status first. It's just some macros the author likes. And strangely presented—why are the LIKELY/UNLIKELY macros thrown in with the CLEANUP one in that first code snippet? That non sequitur is part of what gives me an LLM-written vibe.
- gebdev 11mo agoThis is a great example of how ADTs can be implemented in C by emulating classes, despite the loss in brevity. For the first item on reference counting, batched memory management is a possible alternative that still fits the C style. The use of something like an arena allocator approximates a memory lifetime, which can be a powerful safety tool. When you free the allocator, all pages are freed at once. Not only is this less error prone, but it can decrease performance. There’s no need to allocate and free each reference counted pointer, nor store reference counts, when one can free the entire allocator after argument parsing is done. This also decreases fallible error handling: The callee doesn’t need to free anything because the allocator is owned by the caller. Of course, the use of allocators does not make sense in every setting, but for common lifetimes such as: once per frame, the length of a specific algorithm, or even application scope, it’s an awesome tool!
- lelanthran 11mo ago> This is a great example of how ADTs can be implemented in C by emulating classes, despite the loss in brevity. I don't see it that way, mostly because ADTs don't require automatic destructors or GC, etc, but also because I never considered a unique/shared pointer type to be an abstract data type > When you free the allocator, all pages are freed at once. Not only is this less error prone, but it can decrease performance. How does it decrease performance? My experience with arenas is that they increase performance at the cost of a little extra memory usage.
- gebdev 10mo ago> My experience with arenas is that they increase performance at the cost of a little extra memory usage. ack, thanks! You’re exactly right, I got my words mixed up.
- k1rd 11mo agoSeems like someone should invent C+, which would be C but with the reasonable safety guardrails that C++ implemented in the last 10/20 years.
- varispeed 11mo agoI am not convinced. I much prefer using plain C without superpowers and write extensive test suite and analyse the code multiple times. There is always a chance to miss something, but code without "magic" is much easier to reason about.
- carlos256 11mo agoPlease don't do It.
- jurschreuder 11mo agoAnybody know his github?
- loeg 11mo agoJust use C++. Every single one of these is a horror-show worse reinvention of a C++ feature. Like, if I was stuck in a C codebase today, [[cleanup]] is great -- I've used this in the past in a C-only shop. But vectors, unique_ptrs, and (awful) shared_ptrs? Just use C++.
- kazinator 11mo ago// The Old Way (don't do this) char* include_pattern = NULL; if (optarg) { include_pattern = strdup(optarg); } // ...200 lines later... if (some_error) { if (include_pattern) free(include_pattern); // Did I free it? Did I?? return 1; Nope! // ...200 lines later... // common return block out: free(include_pattern); // free(NULL) allowed since before 1989 ANSI C return result;
- kazinator 11mo agoThis won't play along with setjmp/longjmp exception handling schemes, unfortunately, without a lot of extra effort. You can do cleanup handling that integrates with your exception handling library by using pairs of macros inspired by the POSIX pthread_cleanup_push stuff. #define cleanup_push(fn, type, ptr, init) { \ cleanup_node_t node; \ type ptr = init; do cleanup_push_api(&node, fn, ptr) while (0) #define cleanup_pop(ptr) cleanup_pop_api(ptr) \ } cleanup_push_api places a cleanup node into the exception stack. This is allocated on the stack: the node object. If an exception goes off, that node will be seen by the exception handling which will call fn(ptr). The cleanup_pop_api call removes the top node, doing a sanity check that the ptr in the node is the same as ptr. It calls fn(ptr). The fact that cleanup_push leaves an open curly brace closed by cleanup_pop catches some balancing errors at compile time. The POSIX pthread_cleanup_pop has an extra boolean parameter indicating whether to do the cleanup call or not. That's sometimes useful when the cleanup is only something done in the case of an abort. E.g. suppose tha the "cleanup" routine is rollback_database_transaction. We don't want that in the happy case; in the happy case we call commit_database_transaction.