Live data from Hacker News

Why do we need for an Undefined Behavior Annex to C++

community.intel.com

71–76 of 76 posts

Re: Why do we need for an Undefined Behavior Annex to C++

#71
post #51

I love how in C++ there seems to be this security issue, and now hundreds of people try to come up with different ways on how to tackle the problem. Everybody wants to help and spend their free time on it. You don't see this in other languages so much. People just use those languages and "they" have to fix any flaws. Also keep your bingo cards ready for people saying Rust does not have undefined behaviour. I'm also n…

> You don't see this in other languages so much. People just use those languages and "they" have to fix any flaws. You absolutely do see that elsewhere, and that's why here on HN we have a different new JS framework every day. The safety in Rust (and .NET, and Java) doesn't come from the `unsafe` keyword, it comes from the guarantees that you have when you don't use said keyword. Ownership, garbage collectors, and le…

That sounds great, and that's the narrative you constantly hear. Java, .NET are memory safe.

BUT: they all have more vulnerabilities on average than C++.

So just saying, memory safety alone does almost nothing to make a language safe.

Why would Rust be so much safer than C++ if so almost all other languages also have memory safety but are on average less safe than C++? Meaning more vulnerabilities (so not only memory safety just to be very clear)

Re: Why do we need for an Undefined Behavior Annex to C++

#72
post #33

Earlier quoted context omitted.

I don't want integer overflow to be implementation defined though, I want it undefined so my compiler can optimize my code for the fact that I don't overflow my integers in the first place.

The implementation can define the behavior on overflow to be the optimized behavior. Optimization and UB are orthogonal concepts.

Implementation defined is not much better than undefined. It only requires documenting the behavior, so compilers could just go like "here are all the optimizations that we apply on each target platform for each set of compiler flags". It's not very helpful, you might as well look at the source code of the compiler.

Re: Why do we need for an Undefined Behavior Annex to C++

#73
post #17

Earlier quoted context omitted.

I don’t know. Safe rust has 0 UB although if I recall correctly things are still a bit messy with integer overflow. I could imagine a world where we define annotations around “signed integer will never overflow” that you can add to hit paths but otherwise disallow optimizing around that UB and for all other UB require an explicit annotation acknowledging it or just a warning and a missed optimization instead. The cur…

> Safe rust has 0 UB Safe Rust aims for 0 UB, but I don't think you can make the claim that it absolutely has no UB. This program SEGFAULTs on my system (macOS), because it's reading an invalid memory address due to a stack overflow: const N: usize = 1024*1024*1024; fn main() { let var: [u8; N] = [0; N]; println!("var: {:?}", var); }

x86_64 macOS has tier 1 rust platform support, which I believe means that it's guaranteed that you get a crash on stack overflow and you can't evade stack protection in safe rust.

It's not possible on all platforms, hence the tiers.

Apparently ARM64 macOS has tier 2 rust platform support, which might mean that that this is not true there, but maybe safe rust has some different unrelated soundness issue on this platform.

I only have very surface knowledge about the tier stuff, so maybe someone can correct me.

Re: Why do we need for an Undefined Behavior Annex to C++

#74

Earlier quoted context omitted.

> No integers that I learned about in math class work like that. Did you learn about modular arithmetic in math class? > The only two non-messy well-defined behaviours are 1) bignums by default like in Python, but not suitable for a low level language like Rust; or, 2) trap on overflow, like Ada is supposed to do though it is usually shut off by a pragma. Both of those have significant runtime cost. No, they're not t…

> Did you learn about modular arithmetic in math class? I knew some wiseacre would say that. No those aren't integers, they are equivalence classes of integers. Yes there are times when wraparound is desirable, just like there are when addition mod 12 is desirable (when figuring out times of day). But you don't want the default behaviour of integers to give 8+5=1. If you want that, fine, but ask for it explicitly. Fo…

> those aren't integers, they are equivalence classes of integers.

And they form a ring and sometimes even a field. That's the least messy behavior of any arithmetic type usually implemented on a computer. The only messy part is division, which doesn't match modular division, but it's what you actually want, usually.

Most of the design problems around arithmetic types in programming languages are a result of people "wanting integers" (or "real numbers") rather than facing the reality of the machines they're programming for.

> trap on overflow, like Ada is supposed to do though it is usually shut off by a pragma

Sounds like people "usually" opt for "messy" behavior.

> For example, in Ada, if you want modular arithmetic, just specify it in the variable declaration. That is the right way to do it.

No disagreement there. Unfortunately, almost all non-niche programming languages get arithmetic wrong, ironically, because they're supposed to be the simplest types.

You have to choose from a range of possible behaviors that all have their use cases, and you have to be aware of the limitations of the actual type you're working with, which is not an integer but something finite. The language cannot make that choice for use. What the default is almost doesn't matter because choosing a particular behavior should be clear and simple.

Re: Why do we need for an Undefined Behavior Annex to C++

#75

Earlier quoted context omitted.

> Did you learn about modular arithmetic in math class? I knew some wiseacre would say that. No those aren't integers, they are equivalence classes of integers. Yes there are times when wraparound is desirable, just like there are when addition mod 12 is desirable (when figuring out times of day). But you don't want the default behaviour of integers to give 8+5=1. If you want that, fine, but ask for it explicitly. Fo…

> those aren't integers, they are equivalence classes of integers. And they form a ring and sometimes even a field. That's the least messy behavior of any arithmetic type usually implemented on a computer. The only messy part is division, which doesn't match modular division, but it's what you actually want, usually. Most of the design problems around arithmetic types in programming languages are a result of people "…

It sounds like you're telling me Rust also gets it wrong. C and C++ at least allow the implementation to do the right thing (i.e. trap) on overflow for signed ints, though they mandate doing the wrong thing for unsigned. I'd call Rust, Ada, C, and C++ all niche languages these days though (the niche is low level system work). #1 on TIOBE is Python whose native arithmetic type is arbitrary precision, which is really the right thing to do.

Yes, modular arithmetic is convenient for computers, but it's not integer arithmetic! If you're using machine words to denote actual integers, and your program does something that causes an overflow, that is an error and signalling the error is far better than quietly giving the wrong answer. It's just like in Python where ints are arbitrary precision. They can still get too big for the implementation (i.e. the computer can run out of memory) but that is unquestionably an error condition. Machine integers are the same thing except the constraint is running out of bits in the machine word, rather than running out of memory. If that happens, it's also an error, unless you chose a datatype indicating a different intention.

This all seems obvious to me, but I've seen the same misunderstanding in other places before. I don't understand why it isn't obvious to everyone.

Re: Why do we need for an Undefined Behavior Annex to C++

#76
post #72

Earlier quoted context omitted.

The implementation can define the behavior on overflow to be the optimized behavior. Optimization and UB are orthogonal concepts.

Implementation defined is not much better than undefined. It only requires documenting the behavior, so compilers could just go like "here are all the optimizations that we apply on each target platform for each set of compiler flags". It's not very helpful, you might as well look at the source code of the compiler.

That's still substantially better than undefined, where multiple runs of the same compiler version on the same source with the same settings are allowed to result in different behaviors. I suppose an implementation could define non-deterministic behavior, but that's unlikely and could be forbidden by the standard.

That said, my overall point is that optimization does not rely on undefined behavior at all. It's commonly argued that it does, but there are languages without undefined behavior that have working optimizers, so it's clearly false. Some optimizations for C (and C++) currently depend on undefined behavior, but there's nothing inherent about that dependence.

Post reply on HN