The only solution is using a program analyzer which can prove the absence of certain errors. Frama-C and Astrée come to mind.
Normal static analyzers are best-effort, will have both false positives and false negatives and will also miss errors. This is the difference between testing and verification.
Dynamic analyzers like ASan or valgrind depend on having good code coverage and good value domain coverage. The former should be obvious, the latter will ensure that error-prone code which fails only when variables have specific values is also flagged. E.g: adding two ints is defined for some value ranges, undefined behaviour for others.
Now the main issue with all of the above is that virtually no self-respecting C programmer will use a verifier, so while theoretically possible to write safe C, it is practically almost unheard of - safety-critical domains aside.
There is no good safe, user-friendly alternative to C, all of them are either much more complex languages (Rust, C++), or less performant (Swift, Go) or have tiny marketshare (D, obscure things like Zig, etc)
"C++ ecosystem: For better, for worse - Anastasia Kazakova"
https://www.youtube.com/watch?v=43E5iYzrQn4
Although a C++ talk, she also touches C quite a few times, given the goal to get them to adopt C++ as safety improvement, and naturally how to tailor CLion to the markets they are on.
Much of Rust's complexity is not directly addressing memory safety, it's providing high-level language features. A C-with-borrow-checker would be very different from Rust.
Sure, Rust itself has a number of higher-level features, some of which are a bit problematic by comparison with C (e.g. monomorphizing generics lead to intermediate-code bloat, which causes long compile times) but it turns out that these features are practically needed, either to make programming-in-the-large more feasible or to abstract some underlying details from user-level Rust code so it can keep working even as the compiler and standard library evolve underneath it. C doesn't have these problems being a mature platform, but Rust still does.
I think this is where we start bumping up against the limitations of the universe we live in, because the question becomes "where do we put the bugs?".
The biggest flaw with C is the array bounds checking, especially if that array is on the stack, or other bugs related to "raw" pointers:
- We could enforce things through hardware, but then you've put the bugs in the microcode.
- You can change your language, but then you've put the bugs in your compiler / interpreter / vm.
- You can try and formally verify the correctness of your code, but it's less feasible for large codebases. And what if there's a bug in your proof? (proofs are programs)
I'm not suggesting people use C/C++, but I wouldn't blame C for the problem of natural numbers.
I'd rather put my bugs in one, more easily verifiable place. Verifying all code that's ever going to be compiled is close to impossible. Verifying the compiler / interpreter / VM is hard, but it's much easier.
At the end of the day our field is basically: "get rid of repetitive work". So arguing that it's better to have total manual control (and basically always re-implement things at the point of use) doesn't strike me as the best approach.
Sure, but the number of bounds-checking bugs in, say, the Rust compiler/stdlib is multiple order of magnitudes lower than if all Rust applications had to roll their own bounds-checking code.
memory safety issues account for an overwhelming amount of security bugs¹ and are avoidable by moving to managed languages. Compilers and VMs are very, very good at elimianting this primary source of bugs in code.
The first answer to the C/C++ problem should be to, whenever possible, move to languages that do a large part of the work for you.
In particular in the linux world it seems like a lot of application code that could be written in memory safe languages is still written in plain C.
[1]https://www.zdnet.com/article/microsoft-70-percent-of-all-se...
Precisely that which makes C so performant is also that which leads it to be "unsafe". It is thus up to the programmer to ensure safety in C, his manual managing of that is the price to pay for the performance gained using the language.
When you read about people who have had problems with C, you will find that these are people who never really learned the language and the language is as simple as one can get which should also make one question their abilities.
The first batch would happen in any language, it's an operational issue.
The second batch comes from C and would likely be prevented by PHP being written in idiomatic Rust.
The third batch is a higher level development issue that is cross-language.
I think there's value in removing classes vulnerabilities.
And blaming the operator is NEVER a good idea. It was the exact opposite of this mentality that made us have safe cars and airplanes today.
Before the 70's, the auto industry was doing exactly what you're doing. Once they stopped doing that, people stopped dying for easily preventable issues.
People coming with "no bound checking/type safety/pointer safety/garbage collection in C" often don't get the simple idea they weren't supposed to be there to begin with, and in C the burden of doing in falls on the developer.
There is nothing wrong with setting types manually, managing memory manually, and thinking through how pointers and array boundaries are computed. In fact this is what makes C unbeatable in its niche, and leads to software with very low resource and performance requirements.
Aka, C code is doomed to segfault. The routine easily verifiable work should be done by a machine, aka by the compiler.
> My main idea is that we need to teach C in a way that helps students understand why a very large fraction of critical software infrastructure, including almost all operating systems and embedded systems, is written in C, while also acknowledging the disastrously central role that it has played in our ongoing computer security nightmare. [...]
> We’d like students to be able to answer the question: Is C an appropriate choice for solving this problem? [...] The second big thing each student should learn is: How can I avoid being burned by C’s numerous and severe shortcomings?
[1] https://blog.regehr.org/archives/1393