|
No, it would not be sufficient. “Undefined behavior” is nothing more than “the compiler is allowed to assume that your program does not do this, and can use that assumption to optimize your program.” The compiler can optimize your program to do something completely unexpected when your integers overflow. For example, there are architectures with a special “counter” register that is more efficient to use for loops. This may come with some kind of instruction like “decrement counter and branch if not zero”. The semantics of this counter register are often not a clean match for the semantics of signed integers. For example, the counter may be wider than the integer type you’re using, or it may only support certain types of comparisons. Edit: That’s the example that came to mind. There are a bunch of microoptimizations involving arithmetic or comparisons that are only valid when overflow is not possible. But loop optimizations are the monster that looms over everything in a discussion about signed integer UB in C, because when signed integer overflow is undefined, the compiler is allowed to make tons of inferences about loops that are much harder to reason about otherwise. The compiler can transform variables to use induction, make inferences about how memory accesses work, do certain types of vectorization, etc. If you are curious about the performance impact, try compiling various benchmarks with `-fwrapv` or `-ftrapv` and compare the performance of these benchmarks to what happens when you don’t use either of those flags. Unspecified behavior is actually a lot narrower than it sounds. It sounds like “the compiler can do anything here”, but that’s actually what undefined behavior is. For unspecified behavior, you’re either using an unspecified value, or there’s something with a set of possible behaviors to choose from. For example, evaluation order is unspecified, the sign of certain operations is unspecified, whether two identical string literals compare equal is unspecified, etc. The compiler has to make some choice—function arguments are evaluated in some order, two particular string literals either compare equal or don’t. Consider an optimization like (x+a)/a => x/a+1. This is possible because the compiler assumes “x+a” does not overflow. If overflow were unspecified behavior, something would have to happen when you add x+a, and the result would have to have to trap or result in a value of the correct type. No possible “unspecified” behavior would result in additional precision that prevented overflow (the standard is clear about precision for arithmetic operations). |