• woodruffw 10 hours ago

    Intentionally 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 9 hours 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 9 hours ago

        To 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 9 hours ago

          I 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 9 hours ago

            I 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.)

            • kouteiheika 9 hours ago

              I can't say I agree with this? If C++ had an Rc equivalent (or if you'd write one yourself) it would be just as zero cost as it is in Rust, both in a single-threaded setting and in a multithreaded-setting. "Zero cost abstraction" doesn't mean that it cannot be misused or that it doesn't have any cognitive overhead to use correctly, just that it matches whatever you'd write without the abstraction in place. Plenty of "zero cost" features in C++ still need to you pay attention to not accidentally blow you leg off.

              Simply put, just as a `unique_ptr` (`Box`) is an entirely different abstraction than `shared_ptr` (`Arc`), an `Rc` is also an entirely different abstraction than `Arc`, and C++ simply happens to completely lack `Rc` (at least in the standard; Boost of course has one). But if it had one you could use it with exactly the same cost as in Rust, you'd just have to manually make sure to not use it across threads (which indeed is easier said than done, which is why it's not in the standard), exactly the same as if you'd manually maintain the reference count without the nice(er) abstraction. Hence "zero cost abstraction".

              • woodruffw 9 hours ago

                Sorry, I realized I’m mixing two things in a confusing way: you’re right that C++ could easily have a standard zero-cost Rc equivalent; I’m saying that it can’t have a safe one. I think this is relevant given the weight OP gives to both performance and safety.

              • SR2Z 3 hours ago

                Isn't the point of using atomics that there is virtually no performance penalty in single threaded contexts?

                IMO "zero cost abstraction" just means "I have a slightly less vague idea of what this will compile to."

                • SkiFire13 3 hours ago

                  No, atomics do have a performance penality compared to the equivalent single threaded code due to having to fetch/flush the impacted cache lines in the eventuality that another thread is trying to atomically read/write the same memory location at the same time.

                  • CyberDildonics an hour ago

                    Atomics have almost no impact when reading, which is what would happen in a shared pointer the vast majority of the time.

                    • woodruffw 42 minutes ago

                      > which is what would happen in a shared pointer the vast majority of the time.

                      This seems workload dependent; I would expect a lot of workloads to be write-heavy or at least mixed, since copies imply writes to the shared_ptr's control block.

          • cogman10 9 hours ago

            > very exotic requirements

            I'd be interested to know what you are thinking.

            The primary exotic thing I can imagine is an architecture lacking the ability to do atomic operations. But even in that case, C11 has atomic operations [1] built in. So worst case, the C library for the target architecture would likely boil down to mutex operations.

            [1] https://en.cppreference.com/w/c/atomic.html

            • kouteiheika 8 hours ago

              Well, basically, yeah, if your platform lacks support for atomics, or if you'd need some extra functionality around the shared pointer like e.g. logging the shared pointer refcounts while enforcing consistent ordering of logs (which can be useful if you're unfortunate enough to have to debug a race condition where you need to pay attention to refcounts, assuming the extra mutex won't make your heisenbug disappear), or synchronizing something else along with the refcount (basically a "fat", custom shared pointer that does more than just shared-pointering).

              • colonwqbang 7 hours ago

                Does there exist any platform which has multithreading but not atomics? Such a platform would be quite impractical as you can't really implement locks or any other threading primitive without atomics.

                • addaon 5 hours ago

                  > Does there exist any platform which has multithreading but not atomics?

                  Yes. Also, almost every platform I know that supports multi threading and atomics doesn’t support atomics between /all/ possible masters. Consider a microcontroller with, say, two Arm cores (multithreaded, atomic-supporting) and a DMA engine.

                  • lpribis 32 minutes ago

                    Yes but "atomic" operations with the DMA engine are accomplished through interrupts (atomic) or memory mapped IO configuration (atomic).

                  • cogman10 7 hours ago

                    Certainly such systems can pretty readily exist. You merely need atomic reads/writes in order to implement locks.

                    You can't create userspace locks which is a bummer, but the OS has the capability of enforcing locks. That's basically how early locking worked.

                    The main thing needed to make a correct lock is interrupt protection. Something every OS has.

                    To go fast, you need atomic operations. It especially becomes important if you are dealing with multiple cores. However, for a single core system atomics aren't needed for the OS to create locks.

                    • SkiFire13 3 hours ago

                      > You merely need atomic reads/writes in order to implement locks.

                      Nit: while it's possible to implement one with just atomic reads and writes, it's generally not trivial/efficient/ergonomic to do so without an atomic composite read-write operation, like a compare-and-swap.

                      • colonwqbang 6 hours ago

                        I wrote "multithreaded" but I really meant "multicore". If two cores are contending for a lock I don't see how irq protection help. As long as there is only one core, I agree.

                        • cogman10 5 hours ago

                          On most multicore systems you can pin the IRQ handling to a single core. Pinning locking interrupts to a single core would be how you handle this.

                  • goalieca 8 hours ago

                    Which platforms might that be? Even MIPS has atomics (at least pointer sized last i checked).

                    • cogman10 7 hours ago

                      AFIAK, and I'm not MIPS expert, but I believe it doesn't have the ability to add a value directly to a memory address. You have to do something like

                          // Not real MIPS, just what I've gleaned from a brief look at some docs
                          LOAD addr, register
                          ADD 1, register
                          STORE register, addr
                      
                      The LOAD and STORE are atomic, but the `ADD` happens out of band.

                      That's a problem if any sort of interrupt happens (if you are multi-threading then a possibility). If it happens at the load, then a separate thread can update "addr" which mean the later STORE will stomp on what's there.

                      x86 and ARM can do

                          ADD 1, addr
                      
                      as well as other instructions like "compare and swap"

                          LOAD addr, register
                          MOV register, register2
                          ADD 1, register2
                          COMPARE_AND_SWAP addr, register, register2
                          if (cas_failed) { try again }
                      • unnah 6 hours ago

                        On MIPS you can simulate atomics with a load-linked/store-conditional (LL/SC) loop. If another processor has changed the same address between the LL and SC instructions, the SC fails to store the result and you have to retry. The underlying idea is that the processors would have to communicate memory accesses to each other via the cache coherence protocol anyway, so they can easily detect conflicting writes between the LL and SC instructions. It gets more complicated with out-of-order execution...

                            loop: LL r2, (r1)
                                  ADD r3, r2, 1
                                  SC r3, (r1)
                                  BEQ r3, 0, loop
                                  NOP
                • accelbred 6 hours ago

                  Unfortunately, for C++, thats not true. At least with glibc and libstdc++, if you do not link with pthreads, then shared pointers are not thread-safe. At runtime it will do a symbol lookup for a pthreads symbol, and based off the result, the shared pointer code will either take the atomic or non-atomic path.

                  I'd much rather it didnt try to be zero-cost and it always used atomics...

                  • TuxSH 4 hours ago

                    True, but that's a fault of the implementation, which assumes POSIX is the only thing in town & makes questionable optimization choices, rather that of the language itself

                    (for reference, the person above is referring to what's described here: https://snf.github.io/2019/02/13/shared-ptr-optimization/)

                    • wyldfire 3 hours ago

                      > the language itself

                      The "language" is conventionally thought of as the sum of the effects given by the { compiler + runtime libraries }. The "language" often specifies features that are implemented exclusively in target libraries, for example. You're correct to say that they're not "language features" but the two domains share a single label like "C++20" / "C11" - so unless you're designing the toolchain it's not as significant a difference.

                      We're down to ~three compilers: gcc, clang, MSVC and three corresponding C++ libraries.

                    • woodruffw 6 hours ago

                      This is, impressively, significantly worse than I realized!

                      • eddd-ddde 4 hours ago

                        Why use atomics if you don't need them? There really should just be two different shared pointer types.

                        • accelbred 6 minutes ago

                          I wouldn't mind two types. I mind shared pointer not using atomics if I statically link pthreads and dlload a shared lib with them, or if Im doing clone3 stuff. Ive had multiple situations in which the detection method would turn off atomic use when it actually needs to be atomic.

                      • loeg 39 minutes ago

                        Tecnhically the mutex refcounting example is shown as an example of the before the header the author is talking about. We don't know what they've chosen to implement shared_ptr with.

                        • spacedcowboy 9 hours ago

                          The number of times I might want to write something in C and have it less likely to crash absolutely dwarfs the number of times I care about that code being cross-platform.

                          Sure, cross-platform is desirable, if there's no cost involved, and mandatory if you actually need it, but it's a "nice to have" most of the time, not a "needs this".

                          As for mutex overheads, yep, that's annoying, but really, how annoying ? Modern CPUs are fast. Very very fast. Personally I'm far more likely to use an os_unfair_lock_t than a pthread_mutex_t (see the previous point) which minimizes the locking to a memory barrier, but even if locking were slow, I think I'd prefer safe.

                          Rust is, I'm sure, great. It's not something I'm personally interested in getting involved with, but it's not necessary for C (or even this extra header) to do everything that Rust can do, for it to be an improvement on what is available.

                          There's simply too much out there written in C to say "just use Rust, or Swift, or ..." - too many libraries, too many resources, too many tutorials, etc. You pays your money and takes your choice.

                          • woodruffw 9 hours ago

                            That’s all reasonable, but here’s one of the primary motivations from the post:

                            > We love its raw speed, its direct connection to the metal

                            If this is a strong motivating factor (versus, say, refactoring risk), then C’s lack of safe zero-cost abstractions is a valid concern.

                            • lelanthran 7 hours ago

                              > As for mutex overheads, yep, that's annoying, but really, how annoying ?

                              For this use-case, you might not notice. ISTR, when examing the pthreads source code for some platform, that mutexes only do a context switch as a fallback, if the lock cannot be acquired.

                              So, for most use-cases of this header, you should not see any performance impact. You'll see some bloat, to be sure.

                            • lelanthran 7 hours ago

                              > Intentionally 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.

                              It's an implementation detail. They could have used atomic load/store (since c11) to implement the increment/decrement.

                              TBH I'm not sure what a mutex buys you in this situation (reference counting)

                              • aidenn0 6 hours ago

                                > the shared pointer implementation uses a POSIX mutex

                                Do you have a source for this? I couldn't find the implementation in TFA nor a link to safe_c.h

                                • cryptonector 9 minutes ago

                                  Meh, it could easily use atomics instead, no lock needed.

                                  • kev009 6 hours ago

                                    C11 has a mutex API (threads.h), so why would it rely on POSIX? Are you sure it's not an runtime detail on one platform? https://devblogs.microsoft.com/cppblog/c11-threads-in-visual...

                                    • loeg 28 minutes ago

                                      The article has an excerpt using posix mutexes specifically. But you're right that C11 code can just portably use standard mutexes.

                                        // The old way of manual reference counting
                                        typedef struct {
                                            MatchStore* store;
                                            int ref_count;
                                            pthread_mutex_t mutex;
                                        } SharedStore;
                                    • saurik 9 hours ago

                                      I'd think a POSIX mutex--a standard API that I not only could implement anywhere, but which has already been implemented all over the place--is way more "cross platform" than use of atomics.

                                      • woodruffw 9 hours ago

                                        To lift things up a level: I think a language’s abstractions have failed if we even need to have a conversation around what “cross platform” really means :-)

                                        • jhatemyjob 2 hours ago

                                          If that's the bar, what language's abstractions haven't failed?

                                        • wat10000 4 hours ago

                                          If you're targeting a vaguely modern C standard, atomics win by being part of the language. C11 has atomics and it's straightforward to use them to implement thread-safe reference counting.

                                        • layer8 6 hours ago

                                          The shared-pointer implementation isn’t actually shown (i.e. shared_ptr_copy), and the SharedPtr type doesn’t use a pthread_mutex_t.

                                        • cachius 10 hours ago

                                          A 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://fil-c.org/

                                          • mk89 8 hours ago

                                            Thank you so much for sharing this. I missed the HN post.

                                            This is beautiful!

                                            • 762236 10 hours ago

                                              Why would I want to run a garbage collector and deal with it's performance penalties?

                                              • jerf 9 hours ago

                                                Because 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.

                                                • jesse__ 14 minutes ago

                                                  > Because about 99% of the time the garbage collect is a negligible portion of your runtime

                                                  lol .. reality disagrees with you.

                                                  https://people.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf#:~:te...

                                                  On page 3 they broadly conclude that if you use FIVE TIMES as much memory as your program would if managed manually, you get a 9% performance hit. If you only use DOUBLE, you get as much as a 70% hit.

                                                  Further on, there are comprehensive details on the tradeoffs between style of GC vs memory consumption vs performance.

                                                  ---

                                                  Moving a value from DRAM into a CPU register is an expensive operation, both in terms of latency, and power consumption. Much of the code out in the "real world" is now written in garbage collected languages. Our datacenters are extremely power hungry (as much as 2% of total power in the US is consumed by datacenters), and becoming more so every day. The conclusion here is that garbage collection is fucking expensive, in real-world terms, and we need to stop perpetuating the idea that it's not.

                                                  • hypeatei 9 hours ago

                                                    I think these threads attract people that write code for performance-critical use cases which explains the "cosmic horror" over pretty benign things. I agree though: most programs aren't going to be brought to their knees over some GC sweeps every so often.

                                                    • jvanderbot 4 hours ago

                                                      I think these threads attract people like that, but also people that want to be like that. I've seen a lot of people do "rigor theater", where things like reproduce-able builds, garbage collection, or, frankly, memory safety are just thought terminating cliches.

                                                      • KerrAvon 7 hours ago

                                                        Outside of hobbyist things, performance-critical code is the only responsible use case for a non-memory safe language like C in 2025, so of course it does. (Even that window is rapidly closing, though; languages like Rust and Swift can be better than C for perf-critical things because of the immutability guarantees.)

                                                        • jstimpfle 2 hours ago

                                                          Productivity, portability, stability, mind-share, direct access to OS APIs... there's a lot of reasons to still use C.

                                                          • pjmlp an hour ago

                                                            Only if the OS is written in C, and has its APIs exposed as C APIs to userspace.

                                                            Quite a few OSes don't fit that rule.

                                                          • sramsay 4 hours ago

                                                            I keep hearing this, but I fail to see why "the massive, well-maintained set of critical libraries upon which UNIX is based" is not a good reason to use C in 2025.

                                                            I have never seen a language with a better ffi into C than C.

                                                            • lelanthran 4 hours ago

                                                              > Outside of hobbyist things, performance-critical code is the only responsible use case for a non-memory safe language like C in 2025, so of course it does.

                                                              Maybe; I sometimes write non-hobbyist non-performance-critical code in C.

                                                              I'm actually planning a new product for 2026 that might be done in C (the current iteration of that product line is in Go, the previous iteration was in Python).

                                                              I've few qualms about writing the server in C.

                                                          • Phil_Latio 9 hours ago

                                                            > Because about 99% of the time the garbage collect is a negligible portion of your runtime

                                                            In a system programming language?

                                                            • Snarwin 8 hours ago

                                                              There's plenty of application-level C and C++ code out there that isn't performance-critical, and would benefit from the safety a garbage collector provides.

                                                              • jvanderbot 4 hours ago

                                                                Right, does `sudo` net benefit from removal of heap corruption, out of bounds, or use after free, etc errors that GC + a few other "safeties" might provide? I think so!

                                                              • pjmlp 9 hours ago

                                                                Yes, plenty have been done already so since Lisp Machines, Smalltalk, Interlisp-D, Cedar, Oberon, Sing#, Modula-2+, Modula-3, D, Swift,....

                                                                It is a matter to have an open mindset.

                                                                Eventually system languages with manual memory management will be done history in agentic driven OSes.

                                                                • KerrAvon 7 hours ago

                                                                  Swift, by design, does not have GC.

                                                                  • pjmlp 6 hours ago

                                                                    Chapter 5,

                                                                    https://gchandbook.org/contents.html

                                                                    It would help if all naysayers had their CS skills up to date.

                                                                    • pebal 7 hours ago

                                                                      RC is a GC method and the least efficient one.

                                                                      • winrid 2 hours ago

                                                                        It's the most predictable and has much less overhead than a moving collector.

                                                                        • pjmlp 2 hours ago

                                                                          Only when we forget about the impact of cycle collections, or domino effects stoping the world when there is a cascade of counters reaching zero.

                                                                          The optimisatios needed to improve such scenarions, are akin to a poor man's tracing GC implementation.

                                                                  • jerf 6 hours ago

                                                                    Whether or not GC is a negligible portion of your runtime is a characteristic of your program, not your implementation language. For 99% of programs, probably more, yes.

                                                                    I have been working in GC languages for the last 25 years. The GC has been a performance problem for me... once. The modal experience for developers is probably zero. Once or twice is not that uncommon. But you shouldn't bend your entire implementation stack choice over "once or twice a career" outcomes.

                                                                    This is not the only experience for developers, and there are those whose careers are concentrated in the places where it matters... databases, 100%-utilization network code, hardware drivers. But for 99% of the programs out there, whatever language they are implemented in, GC is not an important performance consideration. For the vast bulk of those programs, there is a much larger performance consideration in it that could be turned up in 5 minutes with a profiler and nobody has even bothered to do that and squeeze out the accidentally quadratic code because even that doesn't matter to them, let alone GC delays.

                                                                    This is the "system programmer's" equivalent of the web dev's "I need a web framework that can push 2,000,000 requests per second" and then choosing the framework that can push 2,001,000 rps over the one that can push 2,000,000 because fast... when the code they are actually writing for the work they are actually doing can barely push 100 rps. Even game engines nowadays have rather quite a lot of GC in them. Even in a system programming language, and even in a program that is going to experience a great deal of load, you are going to have to budget some non-trivial optimization time to your own code before GC is your biggest problem, because the odds that you wrote something slower than the GC without realizing it is pretty high.

                                                                    • Phil_Latio 5 hours ago

                                                                      > Whether or not GC is a negligible portion of your runtime is a characteristic of your program, not your implementation language.

                                                                      Of course, but how many developers choose C _because_ it does not have a GC vs developers who choose C# but then work around it with manual memory management and unsafe pointers? ....... It's > 1000 to 1

                                                                      There are even new languages like C3, Odin, Zig or Jai that have a No-GC-mindset in the design. So why you people insist that deliberately unsafe languages suddenly need a GC? There a other new languages WITH a GC in mind. Like Go. Or pick Rust - no GC but still memory safe. So what's the problem again? Just pick the language you think fits best for a project.

                                                                  • 762236 9 hours ago

                                                                    For new projects, I just use Rust: there is zero reason to deal with a garbage collector today. If I'm in C, it's because I care about predictable performance, and why I'm not using Java for that particular project.

                                                                    • greenavocado 8 hours ago
                                                                      • 762236 7 hours ago

                                                                        I don't understand. This is an optional part of Rust. I'm obviously not using a garbage collector in Rust.

                                                                        • bigstrat2003 6 hours ago

                                                                          Not really sure what relevance a third party library has when discussing the language characteristics.

                                                                    • sesm 8 hours ago

                                                                      IDK about Fil-C, but in Java garbage collector actually speeds up memory management compared to C++ if you measure the throughput. The cost of this is increased worst-case latency.

                                                                      A CLI tool (which most POSIX tools are) would pick throughput over latency any time.

                                                                      • milch an hour ago

                                                                        Depending on the CLI tool you could even forego memory management completely and just rely on the OS to clean up. If your program completely reads arbitrary files into memory it's probably not the best idea, but otherwise it can be a valid option. This is likely at least partly what happens when you run a benchmark like this - the C++ one cleans everything up nicely if you use smart pointers or manual memory management, while the Java tool doesn't even get to run GC at all, or if it does it only cleans up a percentage of the objects instead of all of them.

                                                                        • zozbot234 8 hours ago

                                                                          You also pay for the increased throughput with significant memory overhead, in addition to worst-case latency.

                                                                          • KerrAvon 7 hours ago

                                                                            This. The memory overhead kills you in large systems/OS-level GC. Reducing the working set size really matters in a complex system to keep things performant, and GC vastly expands the working set.

                                                                            In the best cases, you’re losing a huge amount of performance vs. an equivalent non-GC system. In the worst, it affects interactive UI performance with multi-second stalls (a suitably modern GC shouldn’t do this, though).

                                                                          • wavemode 7 hours ago

                                                                            Java (w/ the JIT warmed up) could possibly be faster than C++, if the C++ program were to allocate every single value on the heap.

                                                                            But you're never going to encounter a C++ program that does that, since it makes no sense.

                                                                            • dataflow 8 hours ago

                                                                              > in Java garbage collector actually speeds up memory management compared to C++ if you measure the throughput

                                                                              If I had a dollar for every time somebody repeated this without real-world benchmarks to back it up...

                                                                              • CyberDildonics 7 hours ago

                                                                                I see this claim all the time without evidence, but it's also apples and oranges. In C++ you can avoid heap allocations so they are rare and large. In java you end up with non stop small heap allocations which is exactly what you try to avoid when you want a program to be fast.

                                                                                Basically java gc is a solution to a problem that shouldn't exist.

                                                                              • palata 9 hours ago

                                                                                Easy: because in your specific use-case, it's worth trading some performance for the added safety.

                                                                                • 762236 9 hours ago

                                                                                  If I'm in C, I'm using JNI to work around the garbage collector of Kava

                                                                                  • palata 8 hours ago

                                                                                    Have you ever measured the performance impact of JNI? :-)

                                                                            • purplesyringa 7 hours ago

                                                                              This 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.

                                                                              • fuhsnn 10 hours 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

                                                                              • krapht 10 hours ago

                                                                                C++: "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.

                                                                                • Qwuke 10 hours ago

                                                                                  It'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 10 hours 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 7 hours ago

                                                                                      There was a language called clay that was C compatible but had move semantics, destructors, templates and operator overloading.

                                                                                    • dboon 8 hours ago

                                                                                      No 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 an hour ago

                                                                                        I 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.)

                                                                                      • Lerc 10 hours ago

                                                                                        Perhaps 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.

                                                                                        • sesm 8 hours ago

                                                                                          Embedded CPU vendors not shipping C++ compilers is what usually stops people.

                                                                                          • kjs3 6 hours ago

                                                                                            Yup. 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

                                                                                            • dontlaugh 4 hours ago

                                                                                              The converging is happening in practice. I’m using a keyboard running RMK for firmware, written in Rust.

                                                                                            • bangaladore 4 hours ago

                                                                                              In my experience most chips released in the past 10+ years ship with C++ compilers.

                                                                                              Quite frankly I'm not sure why you wouldn't given that most are using GCC on common architectures. The chip vendor doesn't have to do any work unless they are working on an obscure architecture.

                                                                                            • ikamm 9 hours 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

                                                                                            • loeg an hour ago

                                                                                              Just 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++.

                                                                                              • rurban 10 hours ago

                                                                                                Just don't mix that up with the real safec.h header from safeclib:

                                                                                                https://github.com/rurban/safeclib/tree/master/include

                                                                                                • debugnik 10 hours ago

                                                                                                  How 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

                                                                                                  • rurban 9 hours ago

                                                                                                    It's only regarded as design failure by the linux folks. Maybe because it came from Microsoft, NIH syndrome.

                                                                                                    A global constraint handler is still by far better than dynamic env handlers, and most of the existing libc/POSIX design failures.

                                                                                                    You can disable this global constraint handler btw.

                                                                                                    • loeg 35 minutes ago

                                                                                                      Microsoft doesn't implement Annex K, either. They ship an non-conforming implementation. Literally no one cares about the "standardized" Annex K version.

                                                                                                      • 1718627440 9 hours ago

                                                                                                        > Maybe because it came from Microsoft, NIH syndrome.

                                                                                                        No it is because you still need to get the size calculation correct, so it doesn't actually have any benefit over the strn... family other than being different.

                                                                                                        Also a memcpy that can fail at runtime, seems to be only complicating things. If anything it should fail at compile time.

                                                                                                      • quotemstr 10 hours ago

                                                                                                        FWIW, 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.

                                                                                                    • archargelod 9 hours ago

                                                                                                      You 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

                                                                                                      [1] https://nim-lang.org/blog/2020/10/15/introduction-to-arc-orc...

                                                                                                      [2] https://nim-lang.org/docs/manual_experimental.html#view-type...

                                                                                                      [3] https://nim-lang.org/docs/options.htm

                                                                                                      [4] https://nim-lang.org/docs/system.html#likely.t%2Cbool

                                                                                                      • edwcross 8 hours ago

                                                                                                        The 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 7 hours ago

                                                                                                          yes, it is frustrating. I also am not quite sure what he is referencing and would be interested in trying out cgrep for myself.

                                                                                                        • gebdev 5 hours ago

                                                                                                          This 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 4 hours 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.

                                                                                                          • dboon 8 hours ago

                                                                                                            Nice, 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

                                                                                                            • Dwedit 8 hours ago

                                                                                                              That's the nice thing about macros, you can also have the macro generate C++ code using destructors instead of using the cleanup attribute. As long as your other C code is also valid C++ code, it should work.

                                                                                                              • 1718627440 7 hours ago

                                                                                                                You can use GCC on MS Windows just fine. Installing MSYS2 will also give you a package manager.

                                                                                                                • dboon 7 hours ago

                                                                                                                  Yes, of course. Unfortunately, sometimes you need to link to Windows binaries and therefore need to compile against the Windows ABI.

                                                                                                                  • 1718627440 6 hours ago

                                                                                                                    From https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html

                                                                                                                    > -mabi=name Generate code for the specified calling convention. [...] The default is to use the Microsoft ABI when targeting Microsoft Windows and the SysV ABI on all other systems.

                                                                                                                    > -mms-bitfields Enable/disable bit-field layout compatible with the native Microsoft Windows compiler. [...] This option is enabled by default for Microsoft Windows targets.

                                                                                                                    Doesn't this work in practice, due to bugs?

                                                                                                                    • electroly 5 hours ago

                                                                                                                      (Not OP) The C++ ABI on Windows isn't compatible between g++ and MSVC, even though the C ABI is. Libraries using C linkage should work fine. MinGW-built programs link against the Microsoft C runtime (MSVCRT.DLL by default) which is itself MSVC-built, so linking MinGW programs to MSVC libraries has to work for anything to work.

                                                                                                                • Maxatar 8 hours ago

                                                                                                                  MSVC now supports C17.

                                                                                                                  • atiedebee 7 hours ago

                                                                                                                    Does it support C99 with VLAs yet?

                                                                                                                    • hgs3 2 hours ago

                                                                                                                      It supports C99 minus VLAs. Worth noting that only C99 mandates VLAs. They are optional in C11, C17, and C23.

                                                                                                                    • CyberDildonics 7 hours ago

                                                                                                                      MSVC is also made out of a dozen javascript processes which makes typing text need a beefy computer.

                                                                                                                      • electroly 6 hours ago

                                                                                                                        MSVC 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.

                                                                                                                  • HexDecOctBin 10 hours ago

                                                                                                                    Any hopes that MSVC will add C23 support before 2040?

                                                                                                                  • bad_username 7 hours ago

                                                                                                                    This reminds me that Scott Meyers (IMO rightfully) considered the destructor as the single most important fearure in C++.

                                                                                                                    • JonChesterfield 6 hours ago

                                                                                                                      I too enjoy space leaks on recursion

                                                                                                                    • throwaway889900 6 hours ago

                                                                                                                      Does 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 32 minutes ago

                                                                                                                        That'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.

                                                                                                                      • k1rd 5 hours ago

                                                                                                                        Seems like someone should invent C+, which would be C but with the reasonable safety guardrails that C++ implemented in the last 10/20 years.

                                                                                                                        • hanstospace 10 hours ago

                                                                                                                          I checked Fil-C out and it looks great. How is this different from Fil-C? Apart from the obvious LLVM stuff

                                                                                                                          • woodruffw 9 hours ago

                                                                                                                            Fil-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.)

                                                                                                                          • carlos256 2 hours ago

                                                                                                                            Please don't do It.

                                                                                                                            • jurschreuder an hour ago

                                                                                                                              Anybody know his github?

                                                                                                                              • khaledh 10 hours ago

                                                                                                                                The 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.

                                                                                                                                • jnwatson 8 hours ago

                                                                                                                                  Like 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 8 hours ago
                                                                                                                                    • naasking 9 hours ago

                                                                                                                                      I 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/

                                                                                                                                    • varispeed 4 hours ago

                                                                                                                                      I 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.

                                                                                                                                      • immibis 9 hours ago

                                                                                                                                        This feels AI-generated.

                                                                                                                                        • JonChesterfield 6 hours ago

                                                                                                                                          Guarding builtin_expect with __GNUC__ instead of has_builtin might be what you find in elderly codebases that were fed into an llm

                                                                                                                                          • immibis 3 hours ago

                                                                                                                                            No, that's pretty normal. It's the English writing that feels AI.

                                                                                                                                        • kerkeslager 7 hours ago

                                                                                                                                          I 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.

                                                                                                                                          • binaryturtle 3 hours ago

                                                                                                                                            There are (older) OSes where this is NOT the case. Leaving things un-freed would leak memory after the application has terminated.

                                                                                                                                            • loeg 30 minutes ago

                                                                                                                                              Sure, like prior to the invention of virtual memory. But those days are in the distant past.

                                                                                                                                            • JonChesterfield 6 hours ago

                                                                                                                                              shouldn't be individually allocated either, arenas for the win

                                                                                                                                              • kerkeslager 6 hours ago

                                                                                                                                                I 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.

                                                                                                                                                • lelanthran an hour ago

                                                                                                                                                  >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

                                                                                                                                                  What makes you think that the program is going to exit anytime soon?

                                                                                                                                                  Arenas are useful if you want to execute a complex workflow and when it is over just blow away all the memory it accumulated.

                                                                                                                                                  An HTTP server is the textbook example; one arena per request, all memory freed when the request is completed, but there's many many more similar workflows in a running program.

                                                                                                                                                  Leaving it for program exit is a luxury only available to those writing toy programs.

                                                                                                                                            • miroljub 10 hours ago

                                                                                                                                              Nice 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?

                                                                                                                                              • brabel 10 hours ago

                                                                                                                                                It would be a lot more constructive if you reported a bunch of corner cases where this doesn't work rather than just dismissing this as a toy.

                                                                                                                                                • miroljub 9 hours ago

                                                                                                                                                  No, I don't dismiss anything.

                                                                                                                                                  It's just, I'd rather play with my own toys instead of using someone else's toy. Especially since I don't think it would ever grow up to be something more than a toy.

                                                                                                                                                  For serious work, I'd use some widely used, well-maintained, and battle-tested library instead of my or someone else's toy.

                                                                                                                                                • Borg3 10 hours ago

                                                                                                                                                  Yeah, 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.

                                                                                                                                                  • 1718627440 9 hours ago

                                                                                                                                                    > use goto

                                                                                                                                                    My thoughts as well. The only thing I would be willing to use is the macro definition for __attribute__, but that is trivial. I use C, because I want manual memory handling, if I wouldn't want that I would use another language. And now I don't make copies when I want to have read access to some things, that is simply not at a problem. You simply pass non-owning pointers around.

                                                                                                                                                    • lukan 10 hours ago

                                                                                                                                                      Can you share such a corner case?

                                                                                                                                                      • Borg3 2 hours ago

                                                                                                                                                        No, 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.

                                                                                                                                                      • 1718627440 9 hours ago

                                                                                                                                                        > static char buf[64];

                                                                                                                                                        In a function? That makes the function not-threadsafe and the function itself stateful. There are places, where you want this, but I would refrain from doing that in the general case.

                                                                                                                                                        • Borg3 2 hours ago

                                                                                                                                                          Holy moly.. Thread safety.. Good point and Bad point. I myself use threads sparsly, so I dont intermix calls between threads..

                                                                                                                                                        • kstrauser 10 hours ago

                                                                                                                                                          God 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 10 hours ago

                                                                                                                                                            Goofy 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.

                                                                                                                                                            • kstrauser 10 hours ago

                                                                                                                                                              I meant the collective bulk of legacy C code running the world that we can’t just rewrite in Rust in a finite and reasonable amount of time (however much I’d be all on board with that if we could).

                                                                                                                                                              There are a million internal C apps that have to be tended and maintained, and I’m glad to see people giving those devs options. Yeah, I wish we (collectively) could just switch to something else. Until then, yay for easier upgrade alternatives!

                                                                                                                                                              • mightyham 9 hours ago

                                                                                                                                                                I was also, in fact, referring to the bulk of legacy code bases that can't just be fully rewritten. Almost all good engineering is done incrementally, including the adoption of something like safe_c.h (I can hardly fathom the insanity of trying to migrate a million LOC+ of C to that library in a single go). I'm arguing that engineering effort would be better spent refactoring and rewriting the application in a fully safe language one small piece at a time.

                                                                                                                                                                • kstrauser 9 hours ago

                                                                                                                                                                  I’m not sure I agree with that, especially if there were easy wins that could make the world less fragile with a much smaller intermediate effort, eg with something like FilC.

                                                                                                                                                                  I wholeheartedly agree that a future of not-C is a much better long term goal than one of improved-C.

                                                                                                                                                                  • uecker 5 hours ago

                                                                                                                                                                    I don't really agree, at least if the future looks like Rust. I much prefer C and I think an improved C can be memory safe even without GC.

                                                                                                                                                                    • whytevuhuni 5 hours ago

                                                                                                                                                                      > I think an improved C can be memory safe even without GC

                                                                                                                                                                      That's a very interesting belief. Do you see a way to achieve temporal memory safety without a GC, and I assume also without lifetimes?

                                                                                                                                                                      • uecker 4 hours ago

                                                                                                                                                                        A simple pointer ownership model can achieve temporal memory safety, but I think to be convenient to use we may need lifetimes. I see no reason this could not be added to C.

                                                                                                                                                                        • whytevuhuni 3 hours ago

                                                                                                                                                                          A C with lifetimes would be nice, I agree.

                                                                                                                                                                          Would be awesome if someone did a study to see if it's actually achievable... Cyclone's approach was certainly not enough, and I think some sort of generics or a Hindley-Milner type system might be required to get it to work, otherwise lifetimes would become completely unusable.

                                                                                                                                                                          • uecker 2 hours ago

                                                                                                                                                                            Yes, one needs polymorphism. Let's see. I have some ideas.

                                                                                                                                                      • keyle 10 hours ago

                                                                                                                                                        I 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.

                                                                                                                                                        • pkhuong 10 hours 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/

                                                                                                                                                          It's a lot easier to catch errors of omission in C than it is to catch unintended implicit behavior in C++.

                                                                                                                                                          • debugnik 10 hours ago

                                                                                                                                                            I 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 9 hours ago

                                                                                                                                                              The 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 7 hours ago

                                                                                                                                                                By "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.

                                                                                                                                                                • 1718627440 6 hours ago

                                                                                                                                                                  Yes, I do. I know they are very different from real correctness verifiers, but it's not like the people only using the compiler has no way of preventing trivial UB bugs. UB also is really only a language concept and nobody is writing code for the abstract C machine.

                                                                                                                                                              • uecker 5 hours ago

                                                                                                                                                                IMHO and maybe counterintuitively, I do not think the existence of UB makes it harder to do formal verification or have safe C implementations. The reason is that you can treat it as an error if the program encounters UB, so one can either derive local requirements or add run-time checks (such as Fil-C) and then obtains spatial and temporal isolation of memory object.

                                                                                                                                                            • fransje26 10 hours 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...

                                                                                                                                                              • pjmlp 10 hours ago

                                                                                                                                                                Some C devs will make all kinds of crazy efforts only not to use C++.

                                                                                                                                                                • greenavocado 8 hours ago

                                                                                                                                                                  C++ is edge case hell even for simple looking code

                                                                                                                                                                  • pjmlp 8 hours ago

                                                                                                                                                                    Not really, only in the mind of haters.

                                                                                                                                                                    • greenavocado 5 hours ago

                                                                                                                                                                      Let's start with object construction. You think you're creating an object. The compiler thinks you're declaring a function.

                                                                                                                                                                          Widget w();  // I made a widget, right? RIGHT?
                                                                                                                                                                      
                                                                                                                                                                      Wrong. You just declared a function that takes no parameters and returns a Widget. The compiler looks at this line and thinks "Ah yes, clearly this person wants to forward-declare a function in the middle of their function body because that's a completely reasonable thing to do."

                                                                                                                                                                      Let's say you wise up and try this:

                                                                                                                                                                          Widget w(Widget());  // Surely THIS creates a widget from a temporary?
                                                                                                                                                                      
                                                                                                                                                                      Nope! That's ALSO a function declaration. You just declared a function called w that takes a function pointer (which returns a Widget) as a parameter.

                                                                                                                                                                      The "fix"? Widget w{}; (if you're in C++11 or later, and you like your initializers curly). Widget w = Widget(); (extra verbose). Widget w; (if your object has a default constructor, which it might not, who knows).

                                                                                                                                                                      The behavior CHANGES depending on whether your Widget has an explicit constructor, a default constructor, a deleted constructor, or is aggregate-initializable. Each combination produces a different flavor of chaos.

                                                                                                                                                                      --

                                                                                                                                                                      So you've successfully constructed an object. Now let's talk about copy elision, where the language specification essentially shrugs and says "the compiler might copy your object, or it might not, we're not going to tell you."

                                                                                                                                                                          Widget makeWidget() {
                                                                                                                                                                              Widget w;
                                                                                                                                                                              return w;  // Does this copy? Maybe! Does it move? Perhaps! Does it do neither? Could be!
                                                                                                                                                                          }
                                                                                                                                                                      
                                                                                                                                                                      Pre-C++17, this was pure voodoo. The compiler was allowed to elide the copy, but not required to. So your carefully crafted copy constructor might run, or it might not. Your code's behavior was non-deterministic.

                                                                                                                                                                      "But we have move semantics now!" Return Value Optimization (RVO) and Named Return Value Optimization (NRVO) are not guaranteed, depend on compiler optimization levels, and can be foiled by doing things as innocent as having multiple return statements or returning different local variables.

                                                                                                                                                                          Widget makeWidget(bool flag) {
                                                                                                                                                                              Widget w1; 
                                                                                                                                                                              Widget w2;
                                                                                                                                                                              return flag ? w1 : w2;  // NRVO has left the chat
                                                                                                                                                                          }
                                                                                                                                                                      
                                                                                                                                                                      Suddenly your moves matter again. Or do they? Did the compiler decide to be helpful today? Who knows! It's a surprise every time you change optimization flags!

                                                                                                                                                                      --

                                                                                                                                                                      C++11 blessed us with auto, the keyword that promises to save us from typing out std::vector<std::map<std::string, std::unique_ptr<Widget>>>::iterator for the ten thousandth time. Most of the time, auto works fine. But it has opinions. Strong opinions. About const-ness and references that it won't tell you about until runtime when everything explodes.

                                                                                                                                                                          std::vector<bool> v = {true, false};
                                                                                                                                                                          auto x = v[0];  // x is not bool. x is std::vector<bool>::reference, a proxy object
                                                                                                                                                                          x = false;
                                                                                                                                                                          // v[0] is now... wait, what? Did that work? Maybe! If x hasn't been destroyed!
                                                                                                                                                                      
                                                                                                                                                                          const std::string& getString();
                                                                                                                                                                          auto s = getString();  // s is std::string (copy made), NOT const std::string&
                                                                                                                                                                      
                                                                                                                                                                      You wanted a reference? Too bad! Auto decays it to a value. You need auto& or const auto& or auto&& (universal reference! another can of worms!) depending on your use case. The simple keyword auto has spawned a cottage industry of blog posts explaining when you need auto, auto&, const auto&, auto&&, decltype(auto), and the utterly cursed auto*.
                                                                                                                                                                      • pjmlp 2 hours ago

                                                                                                                                                                        I could write a similar rant based on C and all the dialects that many uneducated on the ways of WG14 assume being proper C, after all I keep track of it since 1991, its UB flaws and security holes that plague C++ due to the base compatibility, but I won't change your mind, nor you will change mine.

                                                                                                                                                                        By the way, C auto now means the same as C++11 auto.

                                                                                                                                                                • capitol_ 8 hours ago

                                                                                                                                                                  I agree, if people just had refrained from building things in c/c++ that operated on data from across a security boundary we wouldn't be in this mess.

                                                                                                                                                                  • ReptileMan 10 hours ago

                                                                                                                                                                    Actually C performs quite good in exactly that area.

                                                                                                                                                                    https://ntrs.nasa.gov/citations/19950022400