15 ms·
I don't understand why people insist on simulating a poor substitute for RAII with a feature that is itself almost decent RAII. > If malloc fails and returns N
by pwdisswordfishz 1y ago
I don't understand why people insist on simulating a poor substitute for RAII with a feature that is itself almost decent RAII.
> If malloc fails and returns NULL, the cleanup function will still be called, and there’s no simple way to add a guard inside free_ptr.
free(NULL) is a no-op, this is a non-issue. I don't know what's so hard about a single if statement anyway even if this were an issue.
- jcupitt 1y ago`free(NULL);` will crash on some platforms that gcc supports, I believe.
- po1nt 1y agoIt shouldn't https://pubs.opengroup.org/onlinepubs/7908799/xsh/free.html https://pubs.opengroup.org/onlinepubs/7908799/xsh/free.html >If ptr is a null pointer, no action occurs.
- quietbritishjim 1y agoWhile I agree it shouldn't, that particular document is the UNIX specification, not the C specification, so it does not apply to C compilers on non-UNIX platforms.
- jibal 1y agofree(NULL) is a noop ever since C89 (I was on the standards committee, X3J11).
- mrheosuper 1y agocan we just do `if(*ptr == NULL) return;` ?
- menaerus 1y agoNo, because optimizing compilers are free to elide the check. https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fdelete-null-pointer-checks https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#ind...
- chongli 1y agoNot on all platforms! If you’re writing portable code targeting a lot of embedded platforms then you don’t want to rely on this optimization.
- menaerus 1y agoIt's a platform-agnostic optimization in case of GCC so if your embedded Linux toolchain is based on GCC, and most of them are, it's pretty much the case that it will have this optimization turned on by default. > This option is enabled by default on most targets. On AVR and MSP430, this option is completely disabled.
- chongli 1y agoYes and if you’re targeting AVR, an extremely popular 8 bit micro, then it’ll be turned off.
- mrheosuper 1y agoI'm not quite familar with this flag, but this >so that if a pointer is checked after it has already been dereferenced, it cannot be null. sound to me that if i've never deref the pointer anytime before(e.g the null check is at the beginning of function), the compiler won't remove this check.
- menaerus 1y agoSince the compiler will merge/fold what it appears to be a different logic sections of your code into a single one, you can never be sure what the release build codegen looks like unless you read the assembly.
- unwind 1y agoThat feels like a "citation needed", since that would be very clear violation of the C spec and thus a rather serious bug in the standard library for that platform.
- lelanthran 1y ago> `free(NULL);` will crash on some platforms that gcc supports, I believe. I'm pretty certain that `free(NULL)` is part of the C99 standard, so compiler vendors have had 25 years to address it. If your `free(NULL)` is crashing on a certain platform, you probably have bigger problems, starting with "Compiler that hasn't been updated in 25 years".
- jibal 1y agoIt's in C89 (I was on the standards committee, X3J11).
- SAI_Peregrinus 1y agoThen it's in violation of the C standard, at least as of C11 (I didn't check C99 or C89). > The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined. Emphasis mine
- jibal 1y ago> `free(NULL);` will crash on some platforms that gcc supports, I believe. No, of course it won't. `free(NULL)` has been a noop ever since C89 (and before, for that matter).
- masklinn 1y ago> I don't understand why people insist on simulating a poor substitute for RAII with a feature that is itself almost decent RAII. Because it’s nowhere near “almost decent RAII” and RAII requires a lot more machinery which makes retrofitting RAII complicated, especially in a langage like C which is both pretty conservative and not strong on types: - RAII is attached to types, so it’s not useful until you start massively overhauling code bases e.g. to RAII FDs or pointers in C you need to wrap each of them in bespoke types attaching ownership - without rust-style destructive moves (which has massive langage implications) every RAII value has to handle being dropped multiple times, which likely means you need C++-style copy/move hooks - RAII potentially injects code in any scope exit, which I can’t see old C heads liking much, if you add copy/move then every function call also gets involved - Because RAII “spreads” through wrapper types, that requires surfacing somehow to external callers Defer is a lot less safe and “clean” than RAII, but it’s also significantly less impactful at a language level. And while I very much prefer RAII to defer for clean-slate design, I’ve absolutely come around to the idea that it’s not just undesirable but infeasible to retrofit into C (without creating an entirely new language à la C++, you might not need C++ itself but you would need a lot of changes to C’s semantics and culture both for RAII to be feasible). https://thephd.dev/just-put-raii-in-c-bro-please-bro-just-one-more-destructor-bro-cmon-im-good-for-it https://thephd.dev/just-put-raii-in-c-bro-please-bro-just-on... has even more, mostly from the POV of backporting C++ so some items have Rust counterpoints… with the issue that they tend to require semantics changes matching Rust which is also infeasible.
- oreally 1y agopoor? If I use RAII I'd need to have a struct/class and a destructor. If I use defer I'd just need the keyword defer and the free() code. It's a lot more lean, efficient, understandable to write out. And with regards to code-execution timing, defer frees me from such a burden compared to if-free.
- Ygg2 1y ago> If I use defer I'd just need the keyword defer and the free() code. Yeah, and not accidentally forgetting to call it. That's the big part. And before "True Scotsman will always free/close/defer!" - No, no they won't. Unless the compiler screams at them, or its enforced via syntax constructs, it will always slip through the cracks.
- oreally 1y agoWell I'd have to pay all the friction of writing up a new type, and in some cases the type gets cubersome. Doubly so if your codebase requires extra some friction like 1 header for each type. Also get over it. We got post-processor things like static analyzers, etc, and whatever AI code reminders/fixers that are coming up next. I'd prefer those over muddying up the code base.
- Ygg2 1y ago> Also get over it. We got post-processor things like static analyzers, etc, and whatever AI code reminders/fixers that are coming up next. Sure. But unless it's part of compiler, someone will not run it, or will run out of resources (no net or no tokens). Defaults matter a ton.
- ncruces 1y ago> I don't understand why people insist on simulating a poor substitute for RAII with a feature that is itself almost decent RAII. RAII doesn't make sense without initialization. Are you proposing C should add constructors, or that C should make do without defer because it can't add constructors?
- masklinn 1y ago> RAII doesn't make sense without initialization. Rust has RAII and does not have constructors.
- ncruces 1y agoRust mandates that every field in a user-defined type is initialized at once. How do you propose to retrofit that into C without "constructors"?
- dwattttt 1y agoC has had designated initializers since C99, if you want you can initialise every struct field at once.
- ncruces 1y agoI encourage you to read (at least) this section of this blog before making simplistic suggestions: https://thephd.dev/just-put-raii-in-c-bro-please-bro-just-one-more-destructor-bro-cmon-im-good-for-it#how-do-you-handle-copies https://thephd.dev/just-put-raii-in-c-bro-please-bro-just-on... How do you mandate initialization, handle copies, move objects, prevent double frees? What's RAII without any of that?
- dwattttt 1y agoYou mandate it like you mandate anything else in C. You don't. You pick C because you want a language that doesn't require a variable to be initialised before mutably referencing it, and you write your defer statements or "destructors" defensively, expecting that a variable could be in any state when it comes time to dispose of it. Or if you find that unacceptable, you accept that C isn't the language you want. There's many other choices available.
- deleted 1y ago[deleted]
- 1718627440 1y agoNot having RAII is precisely the reason I prefer C over C++ or Rust. I WANT to be able to separate allocation from initialization. I'm currently working with Arduino code and the API is a mess. Everything has a second set of manual constructor/destructor, which bypasses type-safety entirely. All only to shoehorn having existing, but uninitialized objects into C++.
- 3836293648 1y agoBoth C++ and Rust allow that? Having niche behaviour not be the default makes sense, but both know it's needed and therefore allow it? (C++ lets you malloc and then placement new (just casting the pointer like C does is UB, but it's being fixed for trivial types) and Rust has both plain alloc and Box<MaybeUninit<T>>) There are a lot of other reasons not to use them, but yours is a made up strawman.
- tomck 1y agoThis isn't what people are talking about, you aren't understanding the problem With RAII you need to leave everything in an initialized state unless you are being very very careful - which is why MaybeUninit is always surrounded by unsafe { Foo f; } f must be initialized here, it cannot be left uninitialized std::vector<T> my_vector(10000); EVERY element in my_vector must be initialized here, they cannot be left uninitialized, there is no workaround Even if I just want a std::vector<uint8_t> to use as a buffer, I can't - I need to manually malloc with `(uint8_t)malloc(sizeof(uint8_t)*10000)` and fill that So what if the API I'm providing needs a std::vector? well, I guess i'm eating the cost of initializing 10000 objects, pull them into cache + thrash them out just to do it all again when I memcpy into it This is just one example of many another one: with raii you need copy construction, operator=, move construction, move operator=. If you have a generic T, then using `=` on T might allocate a huge amount of memory, free a huge amount of memory, or none of the above. in c++ it could execute arbitrary code If you haven't actually used a language without RAII for an extended period of time then you just shouldn't bother commenting. RAII very clearly has its downsides, you should be able to at least reason about the tradeoffs without assuming your terrible strawman argument represents the other side of the coin accurately