67 comments

[ 5.1 ms ] story [ 151 ms ] thread
Setting goals and requirements for code coverage via fuzzing (e.g. libFuzzer or AFL), where risky attack surfaces have stricter goals for coverage, is a great way to get ROI on your time spent securing C++ code.
1. Don't use C++.
No idea why this is being downvoted.
It's a useless dismissal of the article. It's equivalent to saying if you don't want to write any bugs, just don't write software. The advice is useless to someone who wants to make software even though it technically is true.
I dunno, the first bullet point of the article is:

  Understand that there are no safety nets provided by the compiler or runtime while coding in C++.
I took the poster's comment to mean this bullet point is essentially saying "Don't use C++ if you really want to write secure software." If we've learned anything in the 35 or so years since C++ was introduced, it's that compilers and runtimes can do a great deal of good to help developers write secure software.

I mean, another reading of this article is that it's a list of the sharpest edges to watch out for in the C++ language, not a list of secure coding practices. The advice presented here boils down to "avoid these common bear traps and foot guns that are unhelpfully provided for you as part of you C++ starterpack." I think many reasonable people could easily conclude the same as the OP after reading this piece.

Jep. Secure C++ is no C++ - given you don't fall back to C or ASM, and consider safer alternatives
This is correct.

What people often don't think about in the safe languages debate is that it's not about the skill of specific programmers. Yes, good programmers can write secure C or C++ code. That's not the problem.

The problem is what happens as code ages. What happens when bugs are fixed, refactors are done, code is ported to other platforms and OSes, multiple people work on it, and pull requests are merged. Bugs creep in when modifications are made without the full context, hastily, and by people who did not write the original code or when it was written so long ago that they've forgotten the deep details of it.

Safe languages will prevent, catch, or at least "nerf" the most dangerous sorts of bugs such as memory errors. Rust is very good at catching most of them at compile time. Unsafe languages won't and it's easy for bugs to creep in later and then result in catastrophic security failures long after they're released. Look at OpenSSL, a hoary old code base that continues to produce ugly security bugs.

In my experience, language selection is typically based on the overall ecosystem, not on any quality of the language itself. It takes an uncommonly opinionated team to pick anything other than the language whose ecosystem seems like it will let them ship with the least amount of hassle.
Old codebases have a lot of baked-in institutional knowledge and bug fixes. A RiiR effort will introduce new bugs and reintroduce old ones, especially if done "without the full context, hastily, and by people who did not write the original code." Rewrites often fail.

MSFT, Google, Apple, Facebook all have a gazillion lines of C++ and it's not going away any time soon. "Don't use C++" is not practical advice for them and many other companies.

Microsoft Security Response Center official guidelines are:

1 - Use a managed language like C#, Java, Go if automatic memory management is ok

2 - Use a systems language like Rust if automatic memory management is not an option

3- Use C++ alongside Core Guidelines tooling.

Granted, not all Microsoft projects care about those guidelines.

>Safe languages will prevent, catch, or at least "nerf" the most dangerous sorts of bugs such as memory errors

As a former C++ developer, errors leading to crashes (be it due to memory or otherwise) were always more preferable to me than logic errors or race conditions (which can still happen in Rust) that go unnoticed for months/years. I guess 'danger' here is subjective, but thats my view.

How does catching crash bugs lead to more logic bugs? In practice I've never seen this.

In any case we're talking about security here where many crash bugs can in fact be catastrophic security vulnerabilities. If it crashes there's a distinct possibility that the bug is exploitable.

That is not quite what I said. I said I would take a crashing bug over a logic bug. Logic bugs are hard to find because you can't easily test for them. IME, Security vulnerabilities are primarily about functions assuming the input is safe (logic bugs), or file format parsing bugs (logic bugs) or the function being buggy to the point where the caller can poison the stack and/or control EIP. (logic bugs)

For each of these types there are some mitigations available at both the OS and hardware level, but I was making a general point about logic vs crashing bugs.

> If it crashes there's a distinct possibility that the bug is exploitable.

What I mean by crashing is that the program stops executing. Are you saying that crashing bugs make it easier to plant resident code? I'm nota security expert, so I don't quite know what you're referring to.

When C and C++ segfault we can be considered lucky that it happened, instead of corrupting data, that might even be stored somewhere else and go months unnoticed.
Not a useful comment, a better one /might/ be "don't write new software in c++."

But that said, C++ is safer than C.

This is a result of large and complex software developing features that require a large amount of manual and error prone code, whereas C++ can do the work for the developer, and will do it correctly everytime. Are there bad things in C++? of course. But you can avoid the known "bad" features - we can acknowledge things we originally thought would be good were in fact bad, and also recognize they need to remain due to old code - without throwing out all the good features.

Hence you get the "modern C++" subset that all serious large c++ projects use, which is not some horrific wasteland of bad code. Most security bugs that have been in C++ have been as a result of errors inherited from C and, unlike C, can be engineered out.

My suspicion is that the over-representation of C++ in vulnerability statistics is a product of the size of the major C++ codebases, the value of those targets, and the complexity of the products.

> C++ compiler generates the code the programmer asked it to generate

Many C++ programmers would be much happier if this was true. Undefined behavior ends up turning this into "C++ compiler generates the most efficient thing that may or may not correlate with what you intended"

But that is what you asked it to generate.

The gaping chasm between what a C++ programmer intended and what they managed to express by writing C++ is where many (not all, but many) of the problems slip in.

I contend that the most important goal of a programming language is that the author, the compiler and any subsequent maintenance programmers all agree what has been expressed. To the extent that your language fails in this (and I argue C++ fails badly and almost constantly) it is a bad programming language.

The existence (albeit not so common in practice with good tooling) of invalid C++ programs which nevertheless compile is a particularly terrible indictment of the language. In this case the author, and likely maintenance programmers (at least until the compiler gets updated to detect their particular case) believe they have written a C++ program, but the compiler silently does not agree and the results might be anything whatsoever.

It is worth noting that C/C++ are essentially the only holdouts that disagree with your (quite reasonable) conception of a programming language. C/C++ treat programs as vague suggestions of machine code, and the vast intricacy of their compiler optimization pipelines give not one single f*ck about screwing up a program that has UB. This entire ecosystem is littered with user-hostile anti-patterns that put the onus on poor programmers who have had the audacity to...not initialize a variable properly, or overflow an integer.
Comments like this are a dime a dozen on HN. Every time I see one, I wonder what horror raises such ire. I like C++ and use it voluntarily for my research. How did it bite you?
I've been programming in C++, sometimes heavily, since 1998. I worked on V8 for almost 7 years. Those were some of my worst debugging experiences (truth be told, usually at the mercy of my own mistakes in the JIT there.).

They say that you can shoot yourself in the foot in C, and blow your leg off in C++. And that's because UB can mean the compiler's dirty tricks all become part of the mystery as to why exactly a program crashed the way it did. I used to ask as an interview question, "You have a C program that crashes. You add a printf and it stops crashing. Why?" There is no right answer...the list is a mile long of things that could be wrong. Obviously it's a bug in my program, in my code. But damned if every tool in the whole chain is going to be so damn unhelpful in finding it. They tell me it's my fault but they proceed to puke their internals at me and let me try to figure it out! Ugggggh!!!!

So I screw up my program, but BAM! Suddenly I am thrust into a complete alternative reality of machine implementation details, a metric asston of low-level irrelevant information...and somewhere in there are clues only I can work out. It was the optimizer! Because my program triggered UB, some insanely complex chain of compiler reasoning led it to the mess that is there now. I am forced to debug the actual machine code. I must. Or its tendrils, raw addresses spewed by a debugger, trying to picture in my head how the fuck these two datastructures got overlapped in memory wrong but it only shows up in optimized code, but manifests in a super-misleading crash. And it was because a field was uninitialized and for some other damn reason it was just working before, like the memory was already zero'd by accident, because it was reusing some specific pattern of deallocated memory, meaning that something was just kinda magically perched on top of it, hiding that bug....You realize it's a very fragile stack of stones.

And then you realize the whole tower is at risk. The code actually has bugs. Right now. Bugs you haven't found yet. Bugs your idiot savant super-intelligent mega-optimizing compiler is recruiting the sum total of hundreds or perhaps thousands of smart humans who think of new ways to make the code go faster, but never bother to reason about undefined behavior, leaving it entirely questionable as to future results of the program you have written today, or happen to be ever-so-unhappily in charge of, but...but...is full of bugs. UB bugs. Like, the worst kind. In the limit, you must fear that a completely super-intelligent compiler would at this point conclude that all C/C++ code having bugs is wrong and will technically replace it with a no-op. Suddenly computers stop booting after a software update because the AI optimized away the kernel to essentially the minimal thing that can pass the compiler's unit test suite, but otherwise is a no-op, mass hysteria, cats and dogs living together.

Ok, maybe I made that last part up. But yeah, this UB thing is really bad.

I feel like I am taking crazy pills, because to me the experience of working with C++ is like being a grunt stuck on a 2 mile-long digging machine that has all the controls set to "BECAUSE FAST" and the only answer to "why all this?" is the computer-programming-equivalent of a kick in the nuts. I seriously hate that experience. I mean seriously, as a compiler person, I love looking at machine code. But that's no way to debug programs. It sucks. Arguably the absolute first job of a programming language is to try to raise the abstraction level. But we can't. Because this stupid language pukes the machine at us. We are fundamentally incapable of raising the abstraction level with C/C++ and drown in a just total cesspool of wasted human effort.

What's worse. C and C++ are the de facto standard languages for safety critical embedded systems. Aerospace/defense laid the ground work for something better with Ada/SPARK, but the rest of the safety critical industries don't seem to care.

At least now the AUTOSAR committee is taking a look at Rust, which gives me some hope for some sanity in the future here.

Yep. Ada/SPARK would be a much better choice over both C and Rust. People turning to Rust for safety really ought to look into Ada's SPARK subset and its capabilities. If it is truly safety they are looking after, Ada/SPARK is the best option. Ada by itself has a really great type system. You want the length of an array? You can use `X'Length`. You can define types using ranges and so forth. I wish more languages implemented such things.
I do think SPARK is the best in terms of safety, but Rust also provides way better safety mechanisms than almost any other language, so I wouldn't discount it so quickly.

Also Rust, I think, has much better developer ergonomics (outside of the delta types which I would really like to have everywhere), and to improve on the status quo we need something safer, but also something people will use.

In the same vein it's useful that Rust is a bit trendy now, since you'll need a lot of people to advocate for this kind of change successfully.

The worst outcome is sticking with C and C++ any longer. Perfect being the enemy of good shouldn't keep us here.

Slap a new coat of paint on Ada; put in curly braces and 'var', and you got something!
Which is why having been exposed to system programing languages other than C and C++ is an eye opener on how things could look like with a different mindset.
It's not the language that "pukes the machine at us". It's compilers. There's nothing fundamentally about the language that requires this. You can implement C as a dynamic language that detects probably all kinds of UB. It would just be slow, that's why it isn't done usually.
>I contend that the most important goal of a programming language is that the author, the compiler and any subsequent maintenance programmers all agree what has been expressed. To the extent that your language fails in this (and I argue C++ fails badly and almost constantly) it is a bad programming language.

I'd disagree, but only slightly. I think the most important aspect of any programming language is the ecosystem. By that I mean ready access to high quality training material for novices, large community of developers willing to help and mentor, and a healthy ecosystem of tooling, libraries and other infrastructure. I think with a lot that has changed in C++, the language, the 'high quality training material for novices' hasn't kept up. I don't think the language is that appealing to novice programmers anymore. I also don't think its a 'bad' language, but I may agree that its past its prime.

A practice started with C, which C++ had to keep compatible with, given its origin as a C preprocessor.

C's authors were already aware of this issue, hence why in UNIX fashion they outsourced the validation to yet another tool, lint.

Unfortunely to most of us, users of software written in such languages, static analysis keeps being ignored by the large majority (about 11% usage per usual language surveys).

It's not that the compiler does not agree. It's that it operates under the assumption that there is no UB. If the compiler knew for sure that there is going to be UB (which happens at runtime, not compile time) of course it would tell. (Surely in some cases, the compiler needs to be improved to see UB that can be detected statically without much effort).

And if you don't request heavy optimization, what happens when UB occurs is likely just what you expected to happen on that particular machine. If you do request optimization, the results might be more off (perhaps the compiler removes some codepath for example). But I always have the impression that UB horror stories are mainly that: stories. I'm only slightly ashamed that I can't tell one from my personal experiences.

>Undefined behavior ends up turning this into "C++ compiler generates the most efficient thing that may or may not correlate with what you intended"

What normal C++ syntax are you referring to that generates undefined behavior?

For example, incrementing an int until it overflows.
My point was what are examples of normal C++ code where the language obviously deviates from the intention of the programmer and screws them over with undefined behavior. I don't see how any programmer can "intend" to use a data-type assuming an infinite range - or assuming specific wrapping behavior that was never guaranteed to them by the language. If a programmer is unaware that language data types have finite ranges and/or specific wrapping behavior then that is a problem that can be easily solved with training.
> If a programmer is unaware that language data types have finite ranges and/or specific wrapping behavior then that is a problem that can be easily solved with training.

The thing is that everyone recognizes this and yet entire categories of bugs that are easily caught with appropriate language design and tooling nonetheless persist in C and C++. Yes, a programmer who does not understand the footguns involved with e.g. integer overflows can be trained to understand them and avoid them. But for every one you train, there will always be another novice programming ready and willing to use the footguns. And even if you do train every last one and every new one, they still have to practice discipline and be vigilant forever more to avoid these problems.

Discipline doesn't scale. It's far better to enforce good behavior at the language level, rather than expecting you'll be able to train your way around these issues.

It will be difficult to program in any language if one believes that you can store an infinite number series in a fixed data type like int. It's like thinking a hard disk can store infinite data.
Yes, but the problem in a different language comes down to an error message. In C you have UB and it's not defined what you have to expect, which makes it harder to find the cause of an error once it happens.
Leaving out UB, what is the size of an "int"? Any of: 16bit, 32bit, 64bit is correct. However I'm absolutely certain the code you write for an 8bit microcontroller is a lot different from your 64bit server, so why is this language pretending to scale like that? Yeah you can now write "uint8_t" instead of "char", but the point is that the ecosystem is a landmine and all the maimed people are shamed with "you should have read the 400 page map"

At risk of sounding crazy what if we just took this undefined behavior and just defined it? Theres like maybe 100 cpu ISAs in the world we care about, and the web and the transitory nature of tech had made legacy support kinda useless. Life would probably be simpler if we just said "ok heres how these constructs work" and just removed the ambiguity

> Yeah you can now write "uint8_t" instead of "char", but the point is that the ecosystem is a landmine and all the maimed people are shamed with "you should have read the 400 page map"

You're criticizing syntax from 30 years ago. Yes, the backwards compatibility (to me) is a BONUS. I would not want to junk working code that I paid for, just so I can use uint8_t. You have the option to begin a slow migration of old code into more modern code - without paying the massive cost of re-writing from scratch.

I guess I am not able to connect with your criticism since it is far removed from my original point - What are examples of normal C++ syntax where the language screws you over with UB? Its a honest question - I'm not longer a C++ dev but I am curious to know what people are talking about.

I'm not against backwards compatibility, I just think leaving primitive sizes up to shrug emoji wasn't brilliant

Man reasons where C syntax has left me out to dry? Well I once spent a week on a deadline debugging a pointer overflow, where it only happened when the game was fullscreen and not debuggable (circa 2005). Even modern C++ is awful though. I fixed a bug a while ago where an "auto&" was just an "auto", and caused the entire scene graph to get copied and then crash.

Have you ever worked in a code base that was clean of UB? If so, are you SURE? By the way this is not a question I ask of any other language on the planet.

The fact is, you have to carry the cognitive overhead of UB even if you write everything correctly, because in the world of C the compiler it can almost be considered an antagonist if it can get 5% faster.

It would be easier to settle these arguments with code rather than opinions.
It would. Do you have some non-trivial bodies of C++ code that you feel don't exhibit any undefined behaviour ?
Yes, all of the code that I've written over the past 15 years. The last I worked on it, I had zero bugs related to UB. The people who paid me own the code.

In any case, its completely bizarre that I'm asking someone to backup their claim, which I didn't agree with, and you're asking me to prove a negative. I don't know what your intention is.

No offense but that sounds like BS to me. I'd love to see the result of a static analysis tool on your code base
Why is that BS? I prioritized and fixed all major bugs, including UB bugs and tje last I worked on it I had zero outstanding bugs of that kind. I'm not saying I never had any bugs in my code. I don't know how I am supposed to be omniscient about bugs I don't know about.
> all of the code that I've written over the past 15 years. The last I worked on it, I had zero bugs related to UB.

Whether you had "bugs related to UB" isn't what we were discussing though. The question was merely syntax that generates undefined behaviour, which of course C++ is so riddled with that its committee has (no joke) a sub-working group whose task is just to try to enumerate all the Undefined Behaviour in their language so that they can try to answer the question "Which behaviours are undefined?" without saying "Read the entire standards document and let us know if you figure out the answer".

> The people who paid me own the code.

And so I guess that's why you didn't provide any link to it. Alas, as with UFO sightings, visions of Jesus and the like - while you may be firmly convinced for everybody else it's not evidence at all.

> In any case, its completely bizarre that I'm asking someone to backup their claim [...]

This is very much a "What did the Romans ever do for us?" problem. You've had several takers and you've simply insisted that they don't count. Why should anybody offer more examples which you will likewise dismiss? There's a problem with C++, you've decided to ignore it, but Software Engineering is ultimately a practical discipline, your 15 years of C++ code (if we could see it) would demonstrate that.

> You've had several takers and you've simply insisted that they don't count. Why should anybody offer more examples which you will likewise dismiss?

Where are these takers? I must have missed them. I did not dismiss anything, I have replied to most comments. You can't pressure me into accepting arguments that I don't agree with. I will present my perspective in a polite way.

sure. ubsan needs to be in the CI. only then you can try to upgrade to -O3 and -flto. even -fstrict-aliasing is then possible

but in my cases I do even have cbmc in my CI, formal verification. not just a single value is checked, all possible values are. overflows don't just happen with your simple test cases.

I enjoy your word salad of acronyms that mean nothing to me but sound very expert
> What normal C++ syntax are you referring to that generates undefined behavior?

If you have a function that takes two arguments and prints both, and you supply arguments like so:

    my_func(a++, a)
You just generated undefined behavior, because the order in which the expressions inside the function call are evaluated, is not guaranteed (keep in mind the comma inside the function call is not a comma operator, which is one of I think 4 operators that actually does guarantee the evaluation order).

You may say it's up to the programmer to know such rules of the programming language, however there's a lot of such rules which makes programming in C++ like walking through a minefield - yes, at some point you learned where the mines are, but you're supposed to remember about them at all times or you risk stepping on one.

Another issue is how the compiler reacts to this UB - usually in a programming language you'd expect that perhaps in one version the arguments are evaluated from left to right, and in another from right to left, or perhaps in some other version it depends on current program state or some other seemingly random factors. However in C++ UB means that everything can happen, for example, the compiler may no-op the entire function call (or worse, the time-space may collapse)

I consider this to actually be an advantage of C++ - there's a lot on the programmer's shoulders, but that gives more possibilities to the compilers (to optimize).

I'm not an expert on C++ but that's my understanding.

See also: https://news.ycombinator.com/item?id=28449461

An expression like my_func(a++, a) is confusing on all fronts - the intention isn't clear for the compiler, for the code reviewer, for the programmer themselves when they read it 5 years from now, for the next-in-line bloke who gets handed this code to maintain it, etc.

My point was about whether C++ screws you over with UB when the programmers intention can be argued to be obvious. I don't think it does. But I will stand corrected if someone can show me what they're talking about.

It seems like people are making this into a discussion about C++ being a difficult language to learn - which I am not defending.

> My point was about whether C++ screws you over with UB when the programmers intention can be argued to be obvious.

Incrementing a number is obvious and results with UB if incremented past the maximum. Since you get no guarantee whatsoever how the UB will be handled (hence the joke with the end of the space-time, or nasal demons etc.), by my understanding (that I think many experts share), it can indeed screw you up.

Of course you may say a programmer should expect this to happen and so it's just a bug - every UB is.

> It seems like people are making this into a discussion about C++ being a difficult language to learn

I think it's more than that. Even assuming that you never caused UB yourself, you can still work on someone else's code with UB and need to debug it. So C++ is hard to learn AND use.

>if incremented past the maximum.

What would be the intention to increment past the maximum? Incrementing a number infinitely is an infinite series 1+1+1[..] A programmer needs to be aware that you cannot store an infinite series in a data type like int - and this applies to any programming language.

If the programmer is looking to exploit wrapping behavior then it is best not to ASSUME anything, but actually verify what promises the language provides. Its basic sanity checking, you do that before sending parameters to API, receiving parameters, during transfer of ownership of memory, etc, etc. All of this falls under basic programming - regardless of language.

> A programmer needs to be aware

This can be extrapolated to anything that causes UB. A programmer needs to be aware that what he does causes UB. You asked for a normal thing that is being done by a programmer that causes UB, and I think I gave a fair example.

I don't want to aggressively argue a point, just wondering what exactly you want to prove here. It's not like programmers cause UB intentionally or when doing some weird stuff like code golfing. No, programmers code in C++ using standard techniques and fall victim to the UB.

Another example of UB is accessing an array out of bounds. Again, you may say a programmer is expected to rationalize his code in this regard, and yet off-by-one is one of the most common categories of errors in practice.

>You asked for a normal thing that is being done by a programmer that causes UB, and I think I gave a fair example.

Not really. I asked for examples where the language takes the intention of the programmer and turns it into UB. The quote that I responded to "C++ compiler generates the most efficient thing that may or may not correlate with what you intended"

What is the intention of the programmer attempting to store an infinite number series in an int?

In any case, I think we've run of steam here so you have the last word if you'd like.

> An expression like my_func(a++, a) is confusing on all fronts - the intention isn't clear for the compiler, for the code reviewer, for the programmer themselves when they read it 5 years from now, for the next-in-line bloke who gets handed this code to maintain it, etc.

There are two choices here which result in this confusion. C++ made those choices, which you admit are confusing. Other languages did not.

First choice, C++ has the pair of increment operators from C. These operators are a bad idea, so many modern languages either nerf them (e.g. only provide one as a statement like Go) or abolish them altogether (e.g. Rust or Swift).

Second choice, unspecified order of evaluation. In principle the idea here is - maybe we can go faster by making different choices depending on context. In practice the price paid for this potential optimisation is high and the reward dubious. So several modern languages just explicitly define the evaluation order instead.

Thus in something like Rust you can't write my_func(a++, a) but you can write my_func(a+=1, a) and while that's a weird thing to write in Rust it doesn't lead to real confusion let alone Undefined Behaviour. The arguments are evaluated left-to-right, the first argument seemingly increments a and its result is an empty tuple (because it's an assignment), the second argument is a, the variable we recently incremented.

Here's an example where a is an unsigned pointer-sized integer:

https://play.rust-lang.org/?version=stable&mode=debug&editio...

Sure enough with a set to 4000 our output says the second parameter was 4001.

All you're saying is C++ lets you write bad code. I don't accept that such code is normal in C++ - it isn't.

Will someone address my original question?

Here it is again:

"What normal C++ syntax are you referring to that generates undefined behavior? "

The context being that C++ breaks the promise with the programmer by deviating from what was originally intended and generating UB.

You conspicuously don't offer any large bodies of C++ you've written that we could use to provide examples of what "normal" is for you, and so you can feel free to dismiss anything we say as just not "normal" and so irrelevant.

For example if I propose that you might foolishly try to multiply unsigned 16-bit integer variables together, I'm sure you'll say that isn't "normal". Or if I proposed you might try to use the C++ stdlib filesystem API on a server which runs other software, doubtless again that just isn't "normal". And so in each case you can feel comfortable that your "normal" C++ is fine.

It is effectively a No True Scotsman argument, all the examples that would prove you wrong aren't "normal" according to you.

You don't get it. We're talking about completely different things. The context of the post I replied to was about C++ turning the INTENTION of the programmer into UB. The argument isn't that you can't write bad code in C++ - you can write bad code in any language.

There is no valid intention with regards to storing an infinite number series in a finite data storage unit - the example that was brought up.

There is no valid intention about using a construct like my_func(a++, a) - this is a confusing construct and is not valid human-readable code.

This is not normal code that is turned into UB - its bad and invalid code to begin with. In any case it doesn't seem like we're getting anywhere so I will just let you have the last word.

> There is no valid intention with regards to storing an infinite number series in a finite data storage unit - the example that was brought up.

No, the example that was brought up was overflow of a signed integer.

Programming languages do a variety of things here, but you don't seem to be familiar with anything except C++ so let's tour briefly.

BigNum languages. In these languages the limited storage size of a machine register is merely a consideration for optimisations. A programmer who is counting people on Earth, estimating global disk storage capacity in bytes or how many Hydrogen atoms there likely are in the visible Universe, needn't worry that there will be "too many" since the language has their back. There is a limit because machines are finite, but it isn't a real concern. Modern Python and many Lisps are in this family.

What is this "integer" of which you speak? In these languages they chose not to worry about the integer types, or at least not to expose them to programmers. All numeric values are floating point. Tremendously large numbers can be handled, albeit they cease to have integer precision. Javascript is in this category.

You can't do that. In these languages overflow is simply impossible, the compiler is doing enough type checking to figure out that your program could overflow and reject it on this basis. Some languages (including Modern Ada) can do this if you request it for specific variables, but this is how WUFFS works all the time.

Overflow is an Error. In these languages overflow is a runtime error, your program must handle Overflow the same way it's obliged to handle a File Not Found error. This might involve control-flow changes like an Exception and of course you can handle that overflow badly but that's on you. Zig treats overflow as an error.

Runtime failure: In these languages overflow is an immediate runtime failure. Some C and C++ compilers can be told to do this, and it is the behaviour of Rust's integer types in debug builds.

Wrapping Overflow. In these languages overflow causes the number to "wrap" back past the opposite end of its range. This is pretty unintuitive, but it is at least defined. Java does this, most assembly languages do this, and Rust offers Wrapping<T> for integer types to give them this behaviour, it will also do this for integer types in release mode by default.

Undefined Behaviour. This is an extraordinary response. But, it is what C and C++ chose here.

So while you may say there was "no valid intention" the reality is that people simply underestimate how high they might be counting or overestimate how big these integer types are in languages like C++, and so they ran into an overflow, and that's all it takes in C++, whereas in these other languages something better happens. This is perfectly normal code, it may not be the right solution (although in some of the language classes described above it works fine), but "Undefined Behaviour" is an astonishing punishment for this oversight.

> incorrect array access would lead to a runtime exception, whereas in C++ this leads to incorrect memory access or memory corruption in case of writing.

Which is why the STL gives you std::array with a bounds-checked accessor (at()).

If you haven't checked your bounds, use that and handle the exception.

If you use a C-style array, expect C-style memory safety.

Additionally all modern C++ compilers have compiler options to also enable bounds checking in operator[](), which should be taken advantage of.
This is one of major faults with C++; the simpler, more elegant syntax is unsafe-by-default due to backwards compatibility.
True, however how else would Bjarne have sold C++ to C folks, just dive into the Usenet archives to see the flame wars of the time and people counting CPU cycles per language feature.

The same can be told about TypeScript, no matter how good its type system is, it will never prevent JavaScript gotchas, for similar reasons.

I don't think std::array is from the Template Library but is instead simply a type provided by the C++ standard library itself. Accordingly it isn't a great fit for the STL's container concept and has to be special-cased.

Also, once again bad defaults in C++. The default array accessor should be bounds checked. If I'm quite sure I wish to risk unchecked access that is an explicit decision signified by using a different accessor. This is the same mistake as discussed below for integer overflow, if the programmer doesn't specify a non-default behaviour that doesn't mean they spent ages agonising over the fine details of whatever the default behaviour happens to be, it means they didn't realise it mattered. Accordingly obey the principle of least surprise and don't do crazy things by default.

While the defaults isn't what security minded folks would wish for, ISO C++ doesn't prevent a compliant implementatio to have them bounds checked, as such all major C++ compilers do provide compiler options to enable all uses of operator[]() as bounds checked, including interator invalidation.

In C++ circles although STL has long moved beyond its original purpose, most people do refer to the C++ standard library as STL.