Bjarne Stroustrup, creator of C++, has issued a call for the C++ community to defend the programming language, which has been shunned by cybersecurity agencies and technical experts in recent years for its memory safety shortcomings.
C and C++ are built around manual memory management, which can result in memory safety errors, such as out of bounds reads and writes, though both languages can be written and combined with tools and libraries to help minimize that risk. These sorts of bugs, when they do crop up, represent the majority of vulnerabilities in large codebases.
↫ Thomas Claburn at The Register
I mean, it makes sense to me that those responsible for new code to use programming languages that more or less remove the most common class of vulnerabilities. With memory-safe languages like Rust having been around for quite a while now, it’s almost wilful negligence to write new code where security is a priority in anything but such memory-safe languages. Of course, this doesn’t mean you delete any and all existing code – it just means you really need to start writing any new code in safer languages. After all, research shows that even when you only write new code in memory-safe languages, the reduction in vulnerabilities is massive.
This reminds me a lot of those old videos of people responding to then-new laws mandating the use of seat belts in cars. A lot of people didn’t want to put them on, saying things to the tune of “I don’t need one because I’m a good driver”. Even if you are a good driver – which statistically you aren’t – everyone else on the road isn’t. When we see those old videos now, they feel quaint, archaic, and dumb – of course you wear a seat belt, you’d be an irresponsible idiot not to! – but only a few decades ago, those arguments made perfect sense to people.
It won’t be long before the same will apply to people doggedly refusing to use memory-safe languages or libraries/extensions that introduce such safety to existing languages, and Bjarne Stroustrup seems to understand that. Are you really smarter than Bjarne Stroustrup?
It doesn’t take a lot of work to learn how to buckle a seatbelt. It does take a lot of work to learn a new language, and even more to become proficient enough to write production code, and even more yet to become expert with it. So while we may need to adopt new languages, I certainly understand the resistance to doing so.
Fair… but it’s a recurring sentiment that a responsible programmer should at least be dabbling in a new language every few years as part of ongoing learning… if nothing else, to keep their mind limber and pick up new ideas they may want to bring back to their preferred languages.
It’s a recurring sentiment that a “responsible” programmer should waste half their life learning superficially different crap at home while they’re not being paid, so that their employer/s can benefit from having a continual stream of “inexperienced with the tools being used” noobs that fuck everything up.
It’s a recurring sentiment that only 16.7% of (commercial) software development projects are completed on time and within budget; and it’s a recurring sentiment that the quality of software (efficiency, correctness, hassle) is continuing to decline..
It’s a recurring sentiment that “newer everything” (languages, frameworks, cloud, …) isn’t actually helping anything.
Whether you’re a mechanical or civil engineer or a programmer, if you’re more than a mere assembly line worker, you have to keep up with new developments in your industry.
Hell, I’d do it just for my hobby programming alone.
Sure; but keeping up with developments that are actually new doesn’t take very long, and doesn’t involve learning new languages. “Soon” we’ll probably start seeing 100 new AI generated programming languages being created every 5 minutes. Will you dedicate your life to being an unemployed loser that tries to learn every language without having any time left to write any code at all?
Brendan,
I understand why people are tired of the keeping up with new tech rigmarole, we all do eventually. But indeed sometimes this does involve new languages. Not everyone is a fan and that’s ok because there will be C/C++ projects for a very long time. Still though rust is growing and may be a good differentiator especially for new candidates. The industry is big and if you don’t like something you can easily leave those opportunities to other people.
I’d like to do away with the insults. AI generating new languages is an idea I hadn’t contemplated before although honestly I think it might go the other way around: rather than humans learning the AI programming languages, it would be more likely for AI to infer programming from human languages, the opposite of what you proposed. Then again you may have been making a rhetorical statement and not a serious debate.
I accept that some people really don’t like change. However maybe we can agree that evolution tends to keep happening slowly regardless.
I think there are a lot more useful things a developer could spend his time learning than dabbling with yet another new language. When hiring I have at least never looked at a CV and throught wow, i am happy that he has a bit experience with 17 different languages. Learning the basics of a new language is one of the easiest things to do in software engineering. If you think it is a fun activity though, then you should of course go ahead and do it.
With all that is going on in the field and all the new technologies we have gotten over the years, the main challenges are still the same two it has always* been: KISS and coupling.
*) with a reasonably short definition of always
Troels,
How old fashioned of you, haha.
No insult intended, in a way that’s common sense. I’m just pointing out how the outsourcing of candidate recruitment in modern times has emphasized exact keyword matching. They’re looking for people who already have matches on the CV never mind one’s ability to learn.
I bring up the KISS principal frequently myself. There are many different design philosophies. It’s beneficial to have languages that protect developers from memory faults because that’s been a reoccurring source of software faults for half a century now.
I’ve seen the rise of scrum/agile in software teams, these aim at making progress while reducing costs, which makes it popular with project managers. I don’t know if my experience is representative but these tend to be correlated with much less time on design and optimizing – everything is a rapid implementation phase after which it gets shipped without really having placed much time into the high level design.
Maybe I’m a dinosaur but I feel software design and optimization are underrated skills in today’s industry. Many employers just care about doing the job rapidly/cheaply and don’t allocate enough resources to do a good job. I’ve never worked for anybody who said “do the best job you can, money is no issue”. Oh well.
“TrapC has pointers that are memory-safe, addressing the memory safety issue with the two languages. With TrapC, developers write in C or C++ and compile in TrapC, for memory safety.”
https://www.infoworld.com/article/3836025/trapc-proposal-to-fix-c-c-memory-safety.html
Anything but Rust and its awful deviant syntax please, even Ada SPARK would be preferable and safer.
I understood everything except “safer”, what did you mean by that? What specifically do you consider unsafe next to other languages?
Ada SPARK is analogous to MISRA C. It’s a checked subset of Ada that ramps up the safety but people generally don’t program in unless well-paid to do so… and I think I heard that, prior to the plans to copy Rust’s borrow checker, it didn’t allow allocating dynamically chosen amounts of memory at runtime because it couldn’t prove it correct.
Ada SPARK is safer than Rust.
Zayn,
Again, I’m asking you about specifics. They borrowed ideas rust, but what exactly makes you say Ada is safer?
Ada SPARK goes further than Rust by enabling formal proofs of correctness for additional properties, so much preferable for high-assurance systems.
Google is your friend as much as it is mine here.
https://docs.adacore.com/spark2014-docs/html/ug/en/gnatprove.html
All this is by the by though. Rust is disgusting for C programmers.
The other big risk I see with Rust is the seemingly huge swathes of Rust proponents that are emotionally unstable and take any criticism of it as an attack on their person. I certainly wouldn’t hire such a person to work for me.
It’s weird.
Not referring to you here btw.
Zayn,
Ah but google would not tell me what you are thinking.
Ok you meant function contracts. Rust does not formally have those yet, although progress is being made by independent developers & tooling. They haven’t officially been accepted yet, but rust is still young and official support will likely be coming at some point. Although I understand some people are anxious 🙂
In any case I don’t believe there should be shame in copying good ideas. Just as Ada copied ideas from Rust to create Spark, Rust can copy ideas from Ada’s gnatprove to improve itself. The exchange of good ideas is something we should all welcome IMHO.
Sometimes there are elitist personalities around programming languages. It’s not unique to rust, we see it with C and C++ developers too. Same can be said for the OS flamewars and many other topics. But we need to remember that those are edge cases and not strictly representative. I think you’ll find that the vast majority of us are quite reasonable and level headed 🙂
@Alfman
They’re also working on verifier-agnostic attributes for invariants that are akin to how Python provides type annotations for things like MyPy to hook into:
https://github.com/rust-lang/rust/issues/128044
From what I remember, the intent is for them to gracefully degrade to runtime assertions if you don’t have a formal verifier in your toolchain taking responsibility for them.
“””I understood everything except “safer”, what did you mean by that? What specifically do you consider unsafe next to other languages?
Alfman we with a few things we need to get right.
We start off with a simple problem.
“C and C++ are built around manual memory management,”
Is this statement true if you open up C/C++/Object C standards and you find this statement is not in fact true. No where in the C/C++/Object C standard does it state you have to use manual memory management or that you have to have automatic memory management.
The issue with C/C++/Object C with memory issues is the two most evil words in programming “Undefined Behavior”. Yes we have the new tools like trap-c and fil-c but these are not the first memory safe versions of C/C++/Object C. Back in the day there was C/C++ compiler from Microsoft that produced .net programs. .Net has a very safe memory model. Yes the result is over 99$ of the CVE that rust says it solves don’t work under C++ compiler producing .net. Yes this is a full to C++ standard compiler that Microsoft made. Yes the .net memory model perfectly fits in the undefined area of C/C++ standard. Yes safe C++ was made in the 90s but we did not want to pay the performance cost..
https://developers.redhat.com/articles/2022/09/17/gccs-new-fortification-level#
What gets even worse is when you check the features in LLVM and GCC compilers.
Yes the CVE rust claims it protects you from. don’t function under C/C++ as long as you build with GCC/LLVM with the protections on. Yes these protections have a runtime performance cost.
Claiming C/C++ is unsafe does not describe why.
1) C/C++ standards have undefined behavours that really do need to be defined.
2) C/C++ compliers default to building without safe guards enabled even when they have them.
3) Like it or not safe code is slower code. Even rust has to insert runtime checks.
Yes with C and C++ and Object C complier developers have been choosing performance over safety in compiler defaults.
Hard reality here the C/C++ programming language standard contains nothing preventing memory safe C/C++ compiler from being made and use.
Lot of ways from my point of view before making Rust the C and C++ complier defaults should have been changed that safe modes of C and C++ are the default and you have to opt out if you want to chase performance.
Alfman Safer programming language mostly is the programing language that does not contain massive amounts of undefined behavior. Rare cases does a programming language define in the language document/standard something totally insecure. The most common is we go bugger it with the language standard we will not define this and allow the compiler to-do what ever it likes with the unsafe languages. Yes this being the case most unsafe programming languages can be made safe if problem undefined behaviors do in fact get defined.
Yes define if buffer overflow program must stop and comelier must have checks that 100 percent prevent overflows including runtime when required. You can write a stack of generics like this to block 90%+ of call CVE in existence if implemented.
oiaohm,
I’m not familiar with objective-C, but C and C++ don’t have rust’s borrow checker nor do they have the features required for managed memory.
I agree it’s a problem.
If you were to modify C++ to make it safe, you’d be creating a new language. That’s fine, but it’s not what everyone else calls C/C++.
C/C++ are unsafe languages because the responsibility for making that code safe lies with the programmer and not the language, which is very different from memory safe languages. Your assumption that safe code is necessarily slower isn’t automatically true either. We have to compare correct code to correct code. There’s no point in comparing faulty code with buffer overflows to correct code that uses a range check. The safety range check is not superfluous if it protects from undefined behavior. Also many types of safety checks can actually be proven at compile time and therefor optimized away. This accounts for much of rust’s appeal.
Nothing is stopping people from making safer languages that are similar to C/C++. Kochise mentioned one earlier: wind-lang. I’d be ok with new variations on C/C++ too, but it does change the language and that’s why C/C++ haven’t done it. Hypothetically we could add a new “safemode” to C++ that rejects all unsafe code. That’s a swell idea but we mustn’t ignore that there would still be an awful lot of preexisting “unsafe” code. The majority of C/C++ code will still need to be compiled as “unsafe”. It’s going to take a lot of work and time to convert huge libraries of “legacy” C/C++ code to be “safe” even if we modified C/C++ to support it.
I think your under the impression that writing generics is enough but it’s not. The language needs to actually be able to enforce safety too, which means the vast majority of existing C/C++ code won’t compile in a new safemode.
“””I’m not familiar with objective-C, but C and C++ don’t have rust’s borrow checker nor do they have the features required for managed memory.”””
https://pldi24.sigplan.org/details/LCTES-2024-main/16/Foundations-for-a-Rust-Like-Borrow-Checker-for-C
https://repositorio-aberto.up.pt/bitstream/10216/153606/3/647397.pdf
Alfman no here is the fun point. C and C++ standards don’t say you have a borrow checker equally does not say the compiler does not use a borrow checker for Static Analysis and/or dynamic analysis of the code.
Yes the links above have show you can retro fit a borrow checker into C/C++/Object C without changing the language.
“”If you were to modify C++ to make it safe, you’d be creating a new language. That’s fine, but it’s not what everyone else calls C/C++.””
This is the problem you are have. You have not checked if anyone has tested if this is true. Problem is people only recently bothered to check.
“””The language needs to actually be able to enforce safety too, which means the vast majority of existing C/C++ code won’t compile in a new safemode.”””
No stop presuming and you need to stop presuming to understand why existing code would fail.
https://github.com/pizlonator/llvm-project-deluge/blob/deluge/Manifesto.md
Fil-c/deluge work. Has show something horrible interesting. This is designed to build C and C++ code without alterations. Interesting point is forced safe mode of Fil-C when it refused to build a program the program turned out to have a code defect that has not been noticed yet where it going to under the right conditions do something bad and go into undefined C/C++ behaviors that lead to CVE and other problems.
Alfman Fil-C/deluge does not have a unsafe mode and code that does not build is defective and you just have not noticed it yet.
Like it or not Safe C and C++ is possible from the compiler alone. Everything you need to do safe C and C++ fits into what the C and C++ standard currently has as undefined behavior.
Yes the price from the compiler enforcing safety would be existing code fails to build that is already defective code.
“””C/C++ are unsafe languages because the responsibility for making that code safe lies with the programmer and not the language, “””
This line is wrong correct like should look like:.
“””C/C++ are unsafe languages because the responsibility for making that code safe lies with the programmer/++Complier– and not the language, “””
You missed Complier there are may programs that are pure C/C++ that perfect match C/C++ standard that gcc and llvm will just straight up refuse to build because you are going straight into undefined behavior of bad.
The fact the C/C++ standards have been tested and proven that you can add this functionality without being in volition to the standard the C/C++ standards can be extended to be safe. And not safe how rust is. Safe with no unsafe.
oiaohm,
They’re using code transpiler with code to code translations, not native C code. Consider that the eiffel language transpiler is able to output C code implementing contracts that never existed in C. It’s no mystery that this should work, it simply exploits the fact that one turing complete language can be implemented inside of another. It’s just another compilation target.
I’m afraid this assertion is completely wrong. We need to be very clear to distinguish between the ability to implement a feature in a language versus a language actually having that feature. For example I could write a whole java VM in C, every last bit of it, but it still does not follow that the C language has all of java’s features – it objectively does not. Asserting C could be used to build rust features could be true, however asserting C has those rust features is just false.
It would fail because none of the trillions of lines of C code declare memory intentions such that correctness can be enforced by the compiler. At best it could try to infer correctness at runtime. But without rust’s compile time verification you’d end up with more runtime overhead to detect all the memory faults like use after free/double free/memory leak/etc. Unlike the C compiler, rust knows object lifetimes because the language declares them.
Of course the C language could be changed to support this too, but then we’re right back to someone needing to fix all the existing lines of code to use it. Alternately if you wanted to suggest the compiler should heuristically guess the intended memory semantics by analyzing the program, then you’d be opening up a can of worms. Programs spanning multiple shared objects could not be fully analyzed. (ie DLLs are compiled separately from the code that uses them). Even when we compile everything into a signal object file that the compiler can analyze, full behavior analysis might never be completed due to the intricacies of the halting problem. And all this is going to be much slower than a language enforcing declared intents.
I agree that undefined behaviors in C/C++ are bad and should be rectified. However I take issue with many things that you keep saying that are false. Making code safe is not only a matter of changing the compiler, authors are going to have to update their code too. To this end, C developers will need to learn the same lessons that rust developers have been learning.
Alfman
“””Unlike the C compiler, rust knows object lifetimes because the language declares them.”””
Alfman you need to stop presuming. The C language does have defined safe object lifetimes. This is sticking inside C defined behavior..
“””all the memory faults like use after free/double free/memory leak/etc.”””
Everything mentioned here you have gone into undefined functionality of C.
Free and Double free is undefined behavior by C standard that anything happens. Trap-C take the acceptable undefined behavior option that the free command is a no-operation. Instead functions by the C standard when variable comes not accessible it will be freed.
“””Alternately if you wanted to suggest the compiler should heuristically guess the intended memory semantics by analyzing the program, then you’d be opening up a can of worms. “””
This is why we end up with a can of worm with what a C compiler makes is that it is guessing the intended memory semantics due to undefined behavour of the C standard allowing compilers to-do what every the heck they like..
We need to decide what C undefined behaviors should be. Should double free be legal, Should free in fact free memory or should it just be a no-op? Remember both are legal under the C standard and a C compiler is by standard free to choose never to free anything.
Should it be legal for memory to remain allocated once all the pointers reference it are no more?
Alfman basically fill in C/C++/Object C undefined behaviors with security sound answers now you have a language with defined lifetimes on everything.
Alfman pick up the C and C++ standards find a memory issue then check the standard. You will find over and over again this is undefined behavior where it been left up the the compiler to decide what in the heck is right.
Your complete argument is based on that C/C++ does not have lifetime without checking if lifetime could be defined without breaking C/C++ standards . By filling in undefined behaviors so making them defined safe behaviors you get object lifetimes in C/C++. Yes code that breaks by these changes was broken C/C++ code because C/C++ compilers are allowed by the C/C++ standards to change undefined behaviors without notice. Safe defined behaviors used for C/C++ undefined behaviors would still be inside C/c++ standard undefined behavior define..
The C/C++ standards are fixable.
The big thing here is updating these C/C++ undefined behaviors to something safe defined behaviors any code that breaks Altman was broken before you changed them. There is a lot of C/C++ code out there that depending on C and C++ undefined behaviors to function and can be caught out by a simple compiler change.
“”Making code safe is not only a matter of changing the compiler, authors are going to have to update their code too.””
Trap-C also shows some cases where its like all allocations have null termination. So end of allocation pointer +1 is always NULL. So filling in undefined behaviors with defined behaviors may include filling in this is programmer common mistake we write the programmer common programmers intention into the defined behaviors.
Alfman so this comes a question how should we answer the C/C++ undefined behaviors should we be able to load into the compiler the programmer commonly makes these mistake transparently fix them. Filling in the undefined behaviors have a lot of options.
oiaohm,
You are the only person I know who would argue something blatantly wrong as being true. The C language clearly does not have object lifetimes. You don’t even have to take my word for it because your own link goes into detail how they came to overload (ie redefined) pragma and restrict keywords to convey new lifetime information to their custom C compiler. You keep acting like C software can gain safety for nothing, but the authors you link to are admitting my point that software needs to be updated in order to use their new safety features. You aren’t just contradicting me, but them as well.
I’m not denying that someone can implement new safe extensions for C (ideally with appropriate keywords and not hijacking existing ones). However in any case we’d still be looking at trillions of lines of pre-existing C code that would need updates in order to become memory safe. That reality doesn’t go away simply because we modified the C language to support safe memory constructs.
Garbage collection is a completely different approach. Yes of course we can reenvision C as a managed memory language, but this idea is not remotely original. If you want garbage collection, then there are so many options to choose from, almost all of them will be better than using C even if you add managed memory to it. Java,C#,Go, etc have widespread support and mature frameworks. There are also less established languages like Dlang that have done a good job cleaning up C. GC has been the gold standard for memory safety for a long time. However GC is often seen as being a compromise between performance goals and safety goals. Rust’s compile time verification offered something new that didn’t depend on the GC.
Since C is not a garbage collected language, the onus is on the developer to use “free” correctly. C doesn’t have the facilities necessary to support native garbage collection, reflection in particular. Hypothetically we could change C to add these missing features, then C could be like other GC languages that don’t need “free”. But it’s important to keep in mind that C is often chosen for low level and realtime code because it’s NOT garbage collected. So long as C isn’t garbage collected, you don’t get the benefit of claiming that free is a no-op or that “double free” doesn’t matter.
All of your points keep trying argue from a position that C is something that it is not. We can envision changes to C/C++ that could make them memory safe, but then they are no longer the C/C++ languages we were using before. It’s like saying “my car is an airplane, I just have to add wings, propulsion, control surfaces, etc”. Sure, you could certainly add new features to your car to make it more of an airplane, but it does not mean your current car actually has the features of an airplane. That’s patently false.
https://github.com/pizlonator/llvm-project-deluge/blob/deluge/Manifesto.md
“””You are the only person I know who would argue something blatantly wrong as being true. The C language clearly does not have object lifetimes. You don’t even have to take my word for it because your own link goes into detail how they came to overload (ie redefined) pragma and restrict keywords to convey new lifetime information to their custom C compiler. “””
Trap-C did but FIl-c did not use pragma or restrict. Fil-C was able to get lifetimes without needing to use restrict or pragma.
Alfman basically look at fil-c and give me link to where they are redefining restrict/pragma. it does not. Yes it simple make C garbage collected and notice how much code just works.
“””Since C is not a garbage collected language, the onus is on the developer to use “free” correctly. C doesn’t have the facilities necessary to support native garbage collection, “””
So its not true that C does not have the facilities to support native garbage collection. If the target architecture of the c/c++ complier says the architecture has native garbage collection so will that C/C++ this is seen with .net c/c++ compilers and others.
Alfman C standard does have object lifetimes if you say you cannot perform undefined behavior.
“””reflection in particular.”””
Alfman when dwarf is used to display a error under C guess what is there in it unwind feature that right reflection.
https://github.com/pmachata/reflection
Its one of those warped things Alfman you are using ELF based platform Linux the Linux kernel it will be using DWARF and you absolutely do have reflection even under C. Yes this is even a kernel module as access to dwarf reflection.
Alfman quite common targeted platforms of C/C++ in the platform you have reflection and other information/features stored that C/C++ compliers don’t by default take advantage of to prevent double free and buffer overflows and the like.
“””Hypothetically we could change C to add these missing features, then C could be like other GC languages that don’t need “free”. But it’s important to keep in mind that C is often chosen for low level and realtime code because it’s NOT garbage collected. So long as C isn’t garbage collected, you don’t get the benefit of claiming that free is a no-op or that “double free” doesn’t matter.””””
It depends how these the features are done. For userspace programs garbage collected is absolutely an option. Realtime code there is option to use information stored in DWARF and other items to look up memory size pointers are pointing and to look at to see if a pointer is still in fact allocated.
Alfman there is a lot of items that C compilers take the path of failure because performance cost checking. Getting what max or min bounds are before attempting to alter a pointer will cost.
Alfman yes on the reflection and a few other items you need to widen the question to cover the platforms C/C++ is being built for. When you notice those platforms give sometimes all the missing feature it comes hang on why is the C/C++ not taking advantage of them to prevent CVE outright because the feature was provided by the platform to know when you are going to do a buffer overflow or a use after free and so on if you look up the information before you dive off cliff.
Say you’re either speaking out of ignorance or malice without saying your’re speaking out of ignorance or malice.
There’s no way a reasonable person who’s used to C++ can actually look at a Wirth-style language and a language that was intentionally designed to borrow C++’s syntax whenever feasible and say that the syntax that’s 95% C++ and 5% Ocaml is more alien to C++ than the 100% Wirth-style language.
Hell, the most visible divergences from C++ that people tend to complain about (eg. colon-separated postfix type declarations) tend to be things Wirth-style languages had first.
They are both disgusting to look at.
If you feel that strongly about something so similar to C++, I really hope you don’t lose your C++ job before retiring, because the outside world is waiting to give you a huge wake-up call.
Since you asked, Go is a language that looks a lot like C++ and is memory-safe. The only gotcha is that it doesn’t support inheritance, it only supports composition, so if you have some OOP-heavy C++ source code that makes copious use of inheritance, you can’t easily rewrite that code to Go without a pretty much complete rethink of the code..
I have done 99% of my development with Go for the last 5 years or so. It’s lovely.
But obviously no good where C is absolutely required and Rust is trying to force its way into.
I recently asked Claude for a laugh to create Crust. Rust with C syntax. It did a fair good rough plan to be fair.
kurkosdr,
There are many languages that are memory safe by virtue of using runtime checks and a run time a garbage collector, There’s nothing wrong with this and these languages can be quite usable and friendly, however I wouldn’t suggest these to replace unmanaged system programming languages. What’s nice about rust is that it provides compile time memory safety without garbage collection. I accept that not everybody likes rust syntax, but nevertheless compile time memory safety is an extremely important language feature. Hopefully a future standard will allow rust and other safe languages to interoperate safely without having to resort to unsafe C interfaces between them. C has played the role of lowest common denominator for decades, but maybe we should plot a path for the future with safer, higher level, language agnostic linking constructs.
…plus, Go isn’t memory-safe in the presence of data races.
From what I remember, things like slices are implemented using multi-word variables and it’s possible to break things like bounds checks and introduce memory corruption if you accidentally code a data race… something Rust protects against by not allowing data races to compile.
ssokolow (Hey, OSNews, U2F/WebAuthn is broken on Firefox!),
Good point, such races are notoriously difficult for humans to debug. I’m all for “outsourcing” this work to machines.
Bjarne Stroustrup: The person who saw a high-level imperative language that had all the memory-leakage and memory-safety issues of assembly and his first thought was “how can I extend this language to an object-oriented language and pitch it to people as the next big thing”.
And of course, he views the efforts of people who try to rid the world of the pestilence of security vulnerabilities he unleashed as “serious attacks”.
That said, if TrapC or whatever work, that would be good news for legacy code, as long as people are legally obligated to use it.
I quite don’t understand the commotion, there have been literally TONS of “safe compilers” around for AGES. Memory handling has ALWAYS been an issue, but people hate garbage collectors “because these are slow”. Yeah, like your crashed/hacked application would mind…
Try one of these : https://www.safe-c.org/ – https://secure-compilation.github.io/ – http://compcert.inria.fr/verasco/ – https://coq.inria.fr/ – etc
Anyway, if someone is still coding in C/C++ a whole application but the few strategic and time constrained pieces, that’s their fate I guess, and making C/C++ “secure” won’t help much. Sooner or later, it’ll crack somewhere, and the more complex the code become, the harder to maintain despite the “safety” of the compiler. Use the right tool : use the right programming language.
There was a video last year (cppcon) on C++26 features and one of things was:
int a;
int b;
assert(a == b); <— this should compile and work
What would any other language developer do ?
a) Make the compiler slap the developer with a large trout
b) Issue compile time error
c) Make this code compile and work
Guess what C++ developers chose ? I couldn't believe what I was hearing. Guess what, it was neither option a, nor b.
std::span is another nonsense. It doesn't provide ANY bounds safety ("safety" by assert is garbage – asserts don't exist). That's why Microsoft had to make their own gsl::span which does the checks 100% of time.
operator [] and operator * ? Same nonsense for containers. Instead of doing runtime bounds checks 100% time, C++ dvelopers decided to ignore any safety, because reasons.
If there's any chance of introducing safety to language, some creeps are going to complain about mythical performance and unwanted code. Again, there are several choices:
a) Make language verify everything 100% time and make developer explicitly mark "unsafe" blocks if they really know what are they doing
b) Make language verify everything 100% of time and allow disabling of safety checks by option (e.g. -fallow-my-usafe-gargage-code)
c) Ignore everything and cry **THEY** (took our jerbs) are attacking C++
This.
Never had such issues in C# (damn well crafted language) or Erlang (another damn well crafted language).
Kochise
I like C#. I think C is too hands off regarding undefined behaviors. Early on it was probably really convenient to ignore them, but it’s these vary edge cases that are so problematic today. Many languages including C# avoid C/C++ issues with better memory safety and making sure that edge cases actually throw exceptions rather than letting programs make it into production with undefined behaviors. It would be very bad practice in a modern language.
Sometimes I like to consider little “alternatives” like https://doc.wind-lang.me/
Kochise,
I’m not familiar with it but I see that it does exactly what we’re discussing. All operations are checked, definitely an improvement on C/C++.
It might indeed check off lots of boxes, although as developers I think all of us share some degree of reservation in committing to such obscure alternatives regardless of their merits. For all of C/C++’s notorious problems, there’s safety in numbers.
How neat would it be if programmers could just program in whatever language they felt like and the code units could be automatically translated to the desired languages as they’re checked into/out of repos. This way everyone could choose their own language and style according to their preferences. Alas, this breaks down because different languages don’t have the same features/ADTs/frameworks/etc. Although this might be something future AI could tackle. Programming languages code doesn’t have to be an implementations, it can be treated as a specification. The AI’s goal would be to generate the most elegant translation and as long as the translation satisfies the specification then it’s legitimate.
Ideally it would even be transitive A->B->C, A->C, C->A, etc This of course isn’t possible to do using stateless conversion due to the lack of feature parity. But using clever tricks it might be possible to do statefully.. I accept that there would be big challenges, but it’s an interesting idea to ponder. Because a fitness function is built into the problem it should be possible to get very accurate results.
Edit: I went O/T here, but I think it’s an interesting idea.
@Alfman
In environments like .NET and, to some extent, the JVM, you can even take “compiled” code and very easily convert it to another language. You can reliably generate VisualBasic.NET code from compiled C# code for example and vice-versa. The resulting programs are completely human readable and round-trip to the same compiled code. You can also do it with F# and these languages to a lesser extent. It is very close to feasible to have people working together using multiple languages in this kind of environment (but only languages designed to work together).
Even outside of such carefully designed environments, a lot is possible with transcoding from one language to another. Many languages began life not by compiling to machine code for example but first outputting C code which was then compiled by a native C compiler. There is an example of this being done with Rust today.
The problem is that, outside of carefully designed environments like .NET, the semantics, type systems, and resource handling of these languages is too different. You can still convert code from one language to another. It will “work” but the result is going to be something totally foreign looking in the target language and very difficult for a human to read and interact with. Moving it back to the original language will not result in something nice either but maybe something that is even harder to deal with. So, the problem with “everybody writes in the language they want” is that they will not be able to read, understand, interact with, or extend what anybody else has done and vice versa. Very quickly, the code would become something nobody could understand or work with. In fact, it would be easier just to read code in the other original language. There is no universal target that we can translate to and from losslessly that makes the interface work.
It is a nice dream though.
LeFantome,
Yes. It’s one thing to de-compile compiled bytecode back into it’s source form or a form that has a 1:1 mapping with it. For example intermediate bytecodes, or .net languages share common frameworks, etc. I’ve seen de-compilers do that. But just to be clear the idea I was talking about is a lot more complex than this because many languages don’t have 1:1 features or even frameworks. Porting between them involves a lot more work than basic syntactic transformation.
Yes. At university we the eiffel programming language, which did exactly that. In principal every language can be compiled to a turing machine, and conversely turing machines can be implemented in practically any language. I’d compare this to a recursive VM problem. We might emulate motorla on PPC, and emulate PPC on x86, and emulate x86 on ARM, etc. It might work, but each transformation becomes more and more distant to the original and it’s not going to be anywhere as clean as recompiling the source to the final target. The problem with all the intermediate translations is that each translation adds new cruft. This is exactly the reason why I suggested the use of stateful translations rather than stateless ones so that context isn’t lost.
Yes, we can agree this is what would happen using naive mechanical translation.
You are assuming naive translation, but I’m suggesting higher level translations where the source is treated more like a specification than a completed work.
Just as a thought experiment: “Rewrite this program from language X using human readable native frameworks and constructs in language Y.” To pass it must generate equivalent IO on a terminal (for example). This is something a mechanical turk should be able to do, I’m just proposing it be done by AI instead. Of course this would be hard to do, but I don’t think it’s impossible. It would be quite a novel accomplishment.
First: safe STL exists, meaning that [] and * can be bounds checked, the STL has hardened modes, yes, it’s a compiler flag. Google enabled it, and found 0.3% performance hit. [1] So, the performance hit is real: small, but measurable.
The reason is that during initial design [] and * had to completely match their C counterparts, even performance wise. In the 90s, that meant no bounds checking.
Same goes for your declared-but-not-initialized example: initialization would have incurred a measurable cost, erroring out would break C compatibility.
C++ has history, a lot of it. Hell we have files untouched since the mid 2000s in our codebase, while using C++23.
All existing correct code should compile with the new language revision.
I do agree that at the point when span was introduced, ignoring bounds checking was more cultural inertia than a rational decision. And how the hell did they not give it a bounds-checked at() method…
But now we’ll get contracts, and I fully expect the various STLs to use those to do bounds checking everywhere.
As a C++ coder, I’m really interested in rust. Until it stabilises its ABI, so that we don’t need to recompile and redeploy the world for a simple library update, we can’t use it.
Rust has its ideal place in OpenGL, OpenCL, anything with an externally defined C API. Not our usecase.
[1] https://security.googleblog.com/2024/11/retrofitting-spatial-safety-to-hundreds.html
What I don’t get about it is that in safety critical (embedded) systems, no pointers and heap are allowed to be used usually when using c/c++. So rust won’t bring that much there as far as I can see.
Rust has compiler-native sum types (i.e. tagged unions as first-class language features which the compiler can check for correct access to), which does allow more invariants to be reliably encoded in the type system.
Maybe have a look at https://github.com/vovkos/jancy
Interesting… but interesting little languages are a dime a dozen. What matters to me is which languages with inertia can do something… and Rust has inertia.
Microsoft rewrote part of the Windows kernel’s GDI subsystem in Rust. Google’s rewriting parts of Android in it. Espressif hired the author of the esp-rust to work on it full time. Infineon announced official support for Rust for several of their microcontroller families. The Google Chrome team has been working toward better Rust-C++ interop becuase they consider augmenting their C++ with more checked data types to be an insufficient long-term solution. Discord rewrote part of their stack from Go to Rust to get rid of the GC pauses. NPM rewrote part of their stack from Node.js to Rust to stop playing uncaught exception whac-a-mole. I periodically see articles from people working in embedded use-cases (eg. automotive) who are happy with the benefits they got from using Rust. etc. etc. etc.
r0ller,
Sometimes all you need is a microcontroller with global variables, but heaps can be done safely too and a memory safe language would be very helpful. Developers need to audit the software to prove it can’t run out of memory or handles such conditions appropriately. Unfortunately linux (the kernel) doesn’t not handle OOM conditions well. I’m not sure if there are any Linux systems that are certified for life critical applications, but other real time operating systems should be.
One of the things Rust’s type system is being used for in the embedded world is to prevent what are essentially data races on GPIO pins. For example, by encoding the pin’s state (eg. input, output, open drain, etc.) into the type system so the pin’s state can’t be changed by one part of the program while another is using it, and so that it’s a compile-time error to try to write to an IN pin or read from an OUT pin.
The embedded world is so conservative that a lot of them took forever to move to C++ (you can find talks from the 2010s about this very subject). They are conservative and move slowly. If they were to move to Rust, this point in time would probably be too early for them anyway. However, given Rust’s strengths in reliability I definitely expect them to move to it or something similar in the long run.
Many embedded systems run on T-Kernel, the successor to iTRON and TRON, so calling them a bit conservative is a understatement.
r0ller
“”””What I don’t get about it is that in safety critical (embedded) systems, no pointers and heap are allowed to be used usually when using c/c++. So rust won’t bring that much there as far as I can see.””””
This is absolutely false about the heaps and pointers not being in safety critical systems code. Sel4 has the highest rating for safety critical usage is written in C it has pointers and heap usage.
http://web1.cs.columbia.edu/~junfeng/09fa-e6998/papers/sel4.pdf
Yes sel4 has a formal proof that is not using any pointer or heap in unsafe way.
Safety critical normal have formal memory model defining rules and this model normally even include hardware quirks that you must not do. Of course these rules we could have C and C++ complier enforce so blocking 90%+ of CVE from even being possible. Yes comes with less than 1% overhead.
I was a college student when I started working in C++. It was an introductory course in object-oriented programming (OOP) that used this language for teaching. From the very beggining it was clear to me on that this language was an anarchic set of patches on top of the venerable C programming language. Java, for example, was a more sensible approach to OOP.
For instance, is multiple inheritance actually needed? C++ does allow it, while Java does not. In my opinion, time has proven Java right on this point.
If the software you are working on does not need to be close to the hardware, Java or C# are much better choices.
I am a fan of Java, but I cannot imagine attributing the lack of success of this technology in fields like desktop applications to supposed “attacks” by third parties. Java is currently experiencing some decline and I do not attribute it to “attacks”, either. Better technologies are created and their use increases at the expense of others, including my beloved Java: it is that simple.
What one does not expect from an adult like Bjarne Stroustrup is that he has the maturity of a teenager.
memory safe languages often may be challenging to switch to.
certain data structures are challenging when using memory safe language (like linked lists, although i suppose it’s a solved problem by now). new language type might require different data types and algorithms, whole new concepts and certain coding practices may no longer apply.
i would assume that heterogenous structures and passing around pointers will be a challenge.
The solution to “data structures are hard” is clearly just to have a standard library that includes them. It is madness in 2025 that people are still having to roll their own linked-lists, hash tables, and binary trees. Ensuring that we can build on the standard library (extensibility) means that we get the benefits of rolling our own without the wasted effort likely benefiting from better and more proven architecture.
Regardless of Rust, making something like creating a data structure like a linked-list an exception that requires jumping through extra hoops is probably a good thing. It tells you to be very careful or to consider if you should really be doing such a thing at all. Rust of course allows you to create data structures like linked-lists. The fact that it discourages you from doing so by making it a pain is really not a bad thing in my view.
Basic data structures are a bit like crypto algorithms. Yes, you can roll your own. You probably should not. Express your genius and creativity elsewhere. Or, if you are really the right person, put in the effort and write something that the rest of us can use. No matter what, we shouldn’t all be doing it.
Stroustrup is of course correct, C++ Devs can’t stand still or something will pass them by, they either get busy fixing and improving, or get busy learning the inevitable replacement.
Nothing is easier to hit than a stationary target.
C++ although extensive was never launched as a completed project, it was the beginning. Some of the comments around this issue read like a fear of change.
Yes,
C++ needs to change with times.
However it will receive a lot of resistance along the way.
I can see the hesitancy to switch to languages like Rust. The syntax is atrocious. (It comes from an entirely different branch of programming that has roots in OCaml and Erlang). Of course some will love it, I don’t.
Yet…
It is easy to have these safety features in C++. The problem is enforcing them.
Say, we don’t want multiple aliases. This is covered by Rust, Swift and similar languages already. And in C++, it can be achieved by using std::unique_ptr and std::move based non-copyable, non-sharable type.
Yet, std::unique_ptr::get() offers an “escape”. It is not only tempting for developers used to older approached, it is actually required in many cases to do something useful with the references that connect to legacy code. And there is a lot of legacy code.
We can extend this to other safety features like range checks (already exists, almost never used. [] vs at()), dangling references, concurrency boundaries and so on.
Of course we can enforce these at compiler level, and a technically sound DevOps team can make it happen. (With escape hatches for well tested, well known interfaces).
But it would be a very hard uphill battle.
sukru,
I agree. If we did add memory safety to C/C++ my prediction is that it would be a rough road, not necessarily because of language obstacles, but because of logistics and attitudes.
The assurances you get in rust is that every block of code is memory safe by default. The unsafe exceptions are clearly documented and easy to audit. In existing C/C++ code bases it would necessarily be the exact opposite. Everything is unsafe by default. To become safe every calls needs to adhere to new memory borrowing contracts that need to be added. A full conversion would be a long process and that’s only if developers enthusiastically commit to doing it. Otherwise stragglers will keep C unsafe for a lot longer.
We’d end up with long term dichotomy between the two sides and a safe vs unsafe fight would happen for every library dependency. Libraries that aren’t safe would end up littering application software with “unsafe” sections, which not only defeats safe constructs, but it puts the motive of switching into serious jeopardy. Developers will rightly ask what is the benefit when they’re dependent on so much unsafe code anyway. If too many people see the effort as not worthwhile than it could derail the whole safety movement.
If we do get a version of C that supports safety, something I would be extremely interested in is the establishment of cross language calling conventions that can work safely without having to dip into unsafe code. Ideally while we’re at it I’d love to see standard libraries get support for all the other missing features in C calling conventions like namespaces, exceptions, async, parameter types, etc, but this might all be too much to ask for.
For the sake of discussion, one potential con of making C code safe is that it could cause more confusion over what code is safe or not. Right now it’s clear that rust code is safe and C code is unsafe but if C starts to support different modes with safe and unsafe code there may be confusion especially if safe code verification depends on compiler flags that aren’t immediately visible.
Alfman,
It is still possible, especially when large organizations like Google, which cannot reliably move off of millions of lines of C++ code are on the task:
https://bughunters.google.com/blog/6368559657254912/llvm-s-rfc-c-buffer-hardening-at-google
Of course it requires changes including:
1. Human behavior for choosing the right thing.
2. Static and dynamic analysis (coming at a runtime cost). Integrated in software control systems that refuse broken code, and runtime that will crash when there is undefined behavior (instead of silently continuing)
3. New libraries that will accommodate those.
I think the greatest obstacle will come from human resistance to change. The rest are just technical questions to solve.
Not sure what you mean here.
I agree that static analysis works ok in functions/blocks where the alloc and free calls exist in pairs and can prove the absence of bugs/leaks. The problem of course is that compilers can’t simulate every object’s lifespan from beginning to end to check if faults will happen. But if programmers declare intentions/memory contracts then the problem becomes tractable and the compiler can verify that code complies with the declarations.
I agree that dynamic analysis at runtime can always be made to work, just like normal managed memory languages, albeit with runtime overhead like you say.
In principal everything can be rewritten around new libraries, but in practice this may involve a lot of work. I have a work project using openssl and it’s not something I’d relish replacing even though I think there is merit in replacing it.
I’m pretty sure you’re right, I feel that as time goes on this is going to be the biggest impediment to modernizing software.
Alfman,
When offered a choice between easy and right…
This is true. The other problem is literally unsolvable. However what type safe languages does, and what C++ people like Stroustrup offer is forcing memory and thread correctness at local level. That way it would be impossible to leak, as all access is always monitored. (Except escape hatches of course)
Say, you are writing a C++ application, and have a structure like this:
struct Widget {
public:
const string name;
Widget(const string& name) : name(name) {}
}
The compiler could force name only mutated in the constructor, and never allowed to change, by making the const contract stricter than before. (No const_cast in regular code regions). It could even force having all members as const by default, and thread safe access to mutable ones (either with a mutex, or having a single “owner” thread that can access it, with runtime checks to crash program immediate if another thread tries to break it).
So, you either have fully-const-enforced value types that cannot mutate
Or… Move only types that can be mutated, but cannot be shared
Or… mutable types that require a mutex or similar for shared access.
What does it mean for most C++ programs?
They can work as is, with minimal changes.
What does it mean for “critical” sections that require more than the default access?
They would have more code, and slower paths to enforce the behavior. But they would still be possible.
sukru,
I agree that local correctness is the lower hanging fruit, although I think it’s worth having languages that verify correctness across non-local execution units. Consider this: while it’s easier for a compiler to verify against local faults, it’s less probable for a human developer to make local faults too. The faults that we need the most help with are those that are harder to catch.
This all depends on application requirements. I’m sure you’ve seen it too but sometimes nice and tidy academic examples end up getting compromised in actual business applications because reasons. You make up a nice model…and then they change things up on you and then you end up with exceptions to the model. Sometimes the code is a disaster but you’ve got no choice but to proceed with requests from managers.
I agree guidelines can help, but sometimes well meaning guidelines can get in the way. For example some people say that all “goto” is bad, but sometimes goto significantly simplifies code flow. Obviously there are very bad uses of goto, but it doesn’t necessarily mean that all uses are bad. One gripe for C is the inability to “break” out of a specific loop, a really dumb limitation and you can end up with really poor workarounds with more variables and code to avoid a goto.
https://stackoverflow.com/questions/9695902/how-to-break-out-of-nested-loops
We do the best with the tools we have. I don’t necessarily want to prescribe one solution (like rustlang) to people who do not like it, however it’s also clear that we’re not going to solve memory faults unless we all upgrade our tooling.
Alfman goto and label were put in the C standard particularly to get out loops.
“””Obviously there are very bad uses of goto,”””
With modern GCC and LLVM and the like compliers using goto does not create anything bad. Historic C compilers absolutely did make something bad. Nice bit of undefined behavior here. Goto use to be a good way to cause memory leaks because goto by early C compilers were converted into a straight assembler jump. Yes so all that memory allocated inside the loop become a memory leak. You don’t find gcc, llvm or msvc doing this instead they work out hey you just goto to label out of loop now as well as jump I need to clean up some memory. Yes some of these complier will now refuse the build code if you attempt to goto into loop. Yes a rule that you can only goto out of a loop that not a goto implemented loop .Really two rules should be added to the C/C++ standards for goto.
1) goto cannot be used to enter a loop.
2) goto causing execution path to exit a segment yes the {} bit should trigger the memory clean up what would happen when you cross the } this includes stacked segments.
Those two changes goto would be safe. OK still allow coding some highly stupid code but safe.
Please note the behavior of using goto not cleaning memory is still legal by current day C standard because its not defined in the C standard that it mandatory to clean up memory in this case.
Different bits of C standard undefined behavior have been filled in by common C compliers making for most programs a stack of CVE issues not exist already. But this could be expanded a lot.. Yes all buffer overflow errors could be completely nuked at compiler level.
oiaohm,
Some people consider it bad style.
C doesn’t free or allocate heap memory on it’s own, using goto doesn’t change this. Of course the programmer is responsible for the heap as usual.. Goto works fine with the stack. In C++ goto calls destructers on exiting scope.
https://stackoverflow.com/questions/334780/are-goto-and-destructors-compatible
Not if you mean at compile time. C can’t solve all overflow errors because C doesn’t have abstract data types and it doesn’t understand what the programmer is doing. even for the simplest program in the world “int main(int argc, char*argv[]) { printf(“%s”,argv[1]); }” C has no idea if it will overflow/segfault.
C can’t fix this unless we change the way arrays work in C. And we’d have to give up pointers that work as arrays too.
“[…] manual memory management, which can result in memory safety errors, such as out of bounds reads and writes […]” – well, inexperienced or inattentive coders writing bad code, wake me up when this movie actually gets interesting /s
It has deteriorated to a kind of a “bad joke” status, but still, know to use the right tool for the job.
I would not ever like attempts ot make c/c++ more rust-like. I’m quite OK with having hands-tied strict languages which force safety or disallow or discourage “unsafety”. But I also like having language(s) that do not tie your hands too much. And, as always, unsafe code isn’t just a language’s fault, I’d say it’s as much or more the coders’ fault. I lived through the era during which c/c++ teaching was going down and GC-heavy languages were taking off, schools producing particularly narrow-minded coders. Oh and the crowds of able-to-write-a-loop people calling themselves experienced programmers. Yeah, get them to use rust asap.
Languages, 12 a dozen. I wrote my first program in about 1990 and I’d have to try really hard to count all the languages I’ve used. I liked some, I disliked others. Rust, I just found hideous, to an extent that just makes me not want to use it if I have any other choice. And I won’t, unless I’d need to for work (thankfully it’s not an issue currently), in which case most of us work for a living…