Live data from Hacker News

Giving C a superpower: custom header file (safe_c.h)

hwisnu.bearblog.dev

141–150 of 277 posts

Re: Giving C a superpower: custom header file (safe_c.h)

#141
post #94

Earlier quoted context omitted.

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

Not really, only in the mind of haters.

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>>::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 v = {true, false};
    auto x = v[0];  // x is not bool. x is std::vector::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*.

Re: Giving C a superpower: custom header file (safe_c.h)

#142
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!

Re: Giving C a superpower: custom header file (safe_c.h)

#143

Earlier quoted context omitted.

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), o…

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.

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

Re: Giving C a superpower: custom header file (safe_c.h)

#144
post #125

Earlier quoted context omitted.

> Because about 99% of the time the garbage collect is a negligible portion of your runtime In a system programming language?

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 implementati…

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

Re: Giving C a superpower: custom header file (safe_c.h)

#145

Earlier quoted context omitted.

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 impor…

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.

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.

Re: Giving C a superpower: custom header file (safe_c.h)

#146
post #116

Earlier quoted context omitted.

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

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…

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

Re: Giving C a superpower: custom header file (safe_c.h)

#148

Earlier quoted context omitted.

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…

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.

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.

Re: Giving C a superpower: custom header file (safe_c.h)

#149
post #6

Earlier quoted context omitted.

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

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.

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.

Re: Giving C a superpower: custom header file (safe_c.h)

#150
post #148

Earlier quoted context omitted.

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.

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.

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

Post reply on HN