C is a highly deterministic language. It has no "features" per se. If you point to a line of C code, I can tell you with very high certainty what assembly opcode(s) it will compile to. The only thing C does that might be considered a "feature" is memory abstraction, which is kind of important for any large programming project.
Now, if you're trying to advocate using Java or Python or something, you've obviously never worked on embedded software. Not only does any very-high-level language introduce much higher resource requirements, but they are also much less deterministic (which is BAD for spacecraft).
Proper C code is not less stable than any very-high-level language. In fact, there is a lot less that could go wrong.
Unless their static analysis tools just make C or C++ look like Ada.
"The rocket is all C++
The rocket and spacecraft are all C++. On the ground, we use National Instruments LabVIEW extensively." - spacexdevtty [2]
[1]http://www.reddit.com/r/IAmA/comments/1853ap/we_are_spacex_s...
[2]http://www.reddit.com/r/IAmA/comments/1853ap/we_are_spacex_s...
Why is that scary?
You can go a long way to counteracting some of these with automated toolchains.
You can probably go further by using languages which simply eliminate certain classes of error and then having the same level of toolchain elaboration.
That's not to say that C code can't be safe. It can absolutely be made safe. It just costs more, and any system that requires positive human effort is more likely to fail than one that doesn't.
Suppose you have an enum:
enum rocket_states { ROCKET_WAITING, ROCKET_FLYING, ROCKET_FAULT }
And a switch that runs off that enum: switch ( some_rocket_state ) {
ROCKET_WAITING:
wait_foo();
wait_bar();
break;
ROCKET_FLYING:
fly_foo();
break;
}
}
This will compile. (Or maybe it won't, I haven't cut C for some time now).But you've missed the ROCKET_FAULT case. In languages which check for case completeness, this kind of logical construction would cause a compiler failure. ML, F#, Rust etc will all complain that you haven't addressed all cases.
Sometimes this or something like it is added to a code template:
default: assert(false);
Which is defensive code that's great for test. But not as great for rockets. Better if bad code simply can't be written.I believe static analysis tools can now pick up on missing cases, but there are still lots of reasons why C can't be as fully analysed as a language like ML, Haskell, ATS, Rust etc can be. Putting aside the obvious problem of undefined behaviour meaning you really want to push C code through multiple compilers and multiple static analysis tools, there's also the great universal escape hatch for C: direct memory access.
When you can simply go in and change memory, type systems and analysis are strictly speaking no longer going to work without positive human effort to refrain from doing that. Analysis can only make guarantees under certain assumptions; introduce direct memory access and all bets are off.
Yes, discipline and coding standards can also cut this down. Some analysis tools will do partial analysis of memory accesses. But it still requires positive human action. A computing system includes the humans who make it, run it and use it. Cutting down on the avenues for mistakes is a good thing.
int main() {
enum states { A, B, C } x = A;
switch(x) {
case A: break;
case B: break;
// C not handled on purpose
}
return 0;
}
$ g++ -Wall -Werror -o enum enum.cc
cc1plus: warnings being treated as errors
enum.cc: In function ‘int main()’:
enum.cc:3: warning: enumeration value ‘C’ not handled in switch
I like this behavior since I can deliberately leave out a default and make sure I always have every case handled explicitly.I'm ambivalent about whether to have default cases. The assert(FALSE) trick is still useful for catching missing breaks in the final case.
I might not understand what you mean regarding positive human action when talking about using static analysis tools -- are you referring to tools that require a manual process to run and evaluate, rather than having your tools outright fail to produce a program if static analysis isn't completely happy with the source code?
The classic example in C/C++ is avoiding buffer overflow defects. If you code naively to the language, they will creep in. Additional effort is require to apply policies and procedures to prevent such errors from occurring -- enabling warnings, using static analysis, using particular libraries like BString and so on. Further effort is required to ensure compliance.
I use the term "positive" by analogy with legal positivism, which is (very roughly) that law is created by legislative action and doesn't exist until that has occurred. Similarly, many kinds of safety in C don't exist until you have taken that positive step to introduce additional safeguards.
In languages with a more complete view of strings, all of this additional activity is unnecessary. Positive human effort is not required to prevent buffer overflows in such languages. No new procedures, policies, libraries or tools need to be introduced or mandated. The system's baseline configuration is already safe against that class of errors.
Why does this matter?
Two reasons.
Firstly, positive human action is expensive.
Secondly, and this is more important, it's unreliable. Any time your safety depends on somebody always sticking to the rules, always remembering to flick a switch, then safety is not assurable. Humans subvert because they disagree, get distracted, make errors, have simple lapses of memory or judgement and so on.
That you can remedy some or all of these problems through lashing together a large management system is one thing. Whether that was the cheapest overall option is another thing entirely.
Mind you, this is all a bit pie in the sky. Apart from Ada (and even then only partly) I'm not aware of an industrial language that fulfills both my requirements and the requirements of SpaceX that I'm pretending can be solved at a stroke. I suspect Rust will be that language. Let's see.
(Yes the compiler can do some trickery at certain optimization levels but even then, you're able to look at the generated assembly very easily and verify the output you expect to see).
I'm a little surprised they aren't running their own OS with a custom scheduler; I guess the odds of that being bug free vs the odds of running into a Linux bug make it a losing proposition.
Also, the idea that C or even Assembler creates guarantees of stability and predictability is contingent on the CPU design.
But now I am at the limits of my shallow pool of knowledge. The SpaceX stuff demonstrably works; Carmack has done fast control systems in C/C++ and it works. I'm probably making an unnecessary fuss.
I doubt "C is as close as you can get to WYSIWYG" is always entirely correct.
Unless you are talking about embedded processors or old 8 and 16 bit processors, this is no longer true in modern architectures.