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.
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.
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++.
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.
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*.By the way, C auto now means the same as C++11 auto.