[0] https://thephd.dev/c2y-the-defer-technical-specification-its...
[1] https://patchwork.ozlabs.org/project/gcc/list/?series=470822
The fact that go "lifts" the deferred statement out of the block is just another reason in the long list of reasons that go shouldn't exist.
Not only is there no protection against data-races (in a language all about multithreading), basically no static checking for safety, allocation and initialization is easy to mess up, but also defer just doesn't work as it does in C++, Rust, Zig, and any other language that implements similar semantics.
What a joke.
However they rely on Trampolines: https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html
And trampolines need executable stack:
> The use of trampolines requires an executable stack, which is a security risk. To avoid this problem, GCC also supports another strategy: using descriptors for nested functions. Under this model, taking the address of a nested function results in a pointer to a non-executable function descriptor object. Initializing the static chain from the descriptor is handled at indirect call sites.
So, if I understand it right, instead trampoline on executable stack, the pointer to function and data is pushed into the "descriptor", and then there is an indirect call to this. I guess better than exec stack, but still...
(and I hope we get a solution without trampolines for the remaining cases as well)
The always_inline keyword takes care of that here.
So, I completely understand the sentiment, but feel that `defer` is a feature that should hopefully move in the opposite direction, allowing us to rely on less exotic code and expose & resolve some of the surprising failure paths instead!
> 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.
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?
Rust has RAII and does not have constructors.
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.
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.
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-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.
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++.
Which hardly ever makes sense, and is possible with clean C++ anyway...
Usually Arduino code is written by hobbyist that give zero care about "clean and abstraction".
(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.
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".
> 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
>If ptr is a null pointer, no action occurs.
No, of course it won't. `free(NULL)` has been a noop ever since C89 (and before, for that matter).
If you want to and/or can, then go ahead. This is for those people who either don't want to, or can't, use C++.
Are you suggesting only use C++ over C in all situations?
If we're referring to the "C is a subset of C++" / "C++ is a superset of C" idea, then this just hasn't been the case for some time now, and the two continue to diverge. It came up recently, so I'll link to a previous comment on it (https://news.ycombinator.com/item?id=45268696). I did reply to that with a few of the other current/future ways C is proposing/going to diverge even further from C++, since it's increasingly relevant to the discussion about what C2y (and beyond) will do, and how C code and C++ code will become ever more incompatible - at least at the syntactic level, presuming the C ABI contains to preserve its stability and the working groups remain cordial, as they have done, then the future is more "C & C++" rather than "C / C++", with the two still walking side-by-side... but clearly taking different steps.
If we're just talking about features C++ has that C doesn't, well, sure. RAII is the big one underpinning a lot of other C++ stuff. But C++ still can't be used in many places that C is, and part of why is baggage that features like RAII require (particularly function overloading and name mangling, even just for destructors alone)... which was carefully considered by the `defer` proposals, such as in N3488 (recently revised to N3687[0]) under section 4, or in other write-ups (including those by that proposal's author) like "Why Not Just Do Simple C++ RAII in C?"[1] and under the "But… What About C++?" section in [2]). In [0] they even directly point to "The Ideal World" (section 4.3) where both `defer` and RAII are available, since as they explain in 4.2, there are benefits to `defer` that RAII misses, and generally both have their uses that the other does not cleanly (if at all) represent! Of course, C++ does still have plenty of nice features that are sorely missing in C (personally longing for the day C gets proper namespaces), so I'm happy we always have it as an option and alternative... but, in turn, I feel the same about C. Sadly isn't as simple to "just use C++" in several domains I care about, let alone dealing with the "what dialect of C++" problem; exceptions or not, etc, etc...
[0]: https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3687.htm [1]: https://thephd.dev/just-put-raii-in-c-bro-please-bro-just-on... [2]: https://thephd.dev/c2y-the-defer-technical-specification-its...
Which I find sad actually. The idea of C++ as a superset of C is really powerful, especially when mixing C and C++. A while ago I had a C project (firmware for a microcontroller) and wanted to bake the version and the compilation time into the firmware. I didn't find a way to do this in plain C, but in C++ you can initialize a global struct and it gets statically linked into the output. This didn't even use constexpr, just preprocessor trickery. Then it was just a matter of renaming the c file to cpp and recompiling. I guess you could also do that with C, but there are things like RAII or constexpr or consuming a C++ library that you can't do without.
Unless we are speaking about PICs or similar old school 8 and 16 bit CPUs, with compilers like those from MIKROE, there is hardly a platform left were the vendor compiler isn't C and C++ (even if it doesn't go beyond C++11).
And if it must be deployed as freestanding, there are still enough improvements to take advantage of.
In the end it boils down to human factor in most cases, however as Dan Saks puts "If you're arguing, you're losing.", taken from
CppCon 2016: “extern c: Talking to C Programmers about C++”
I think in the spirit of C, this should go into the linker, not in the compiler.