Remix.run Logo
keyle 6 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 6 hours ago | parent | next [-]

> 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 6 hours ago | parent [-]

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.

uecker an hour ago | parent | next [-]

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.

1718627440 5 hours ago | parent | prev [-]

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 3 hours ago | parent [-]

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 2 hours ago | parent [-]

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.

fransje26 6 hours ago | parent | prev | next [-]

> 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 6 hours ago | parent | prev | next [-]

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

greenavocado 4 hours ago | parent [-]

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

pjmlp 4 hours ago | parent [-]

Not really, only in the mind of haters.

greenavocado 2 hours ago | parent [-]

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*.
capitol_ 5 hours ago | parent | prev | next [-]

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 6 hours ago | parent | prev [-]

Actually C performs quite good in exactly that area.

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

fransje26 6 hours ago | parent [-]

And

https://hackaday.com/2024/02/10/the-usage-of-embedded-linux-...