The first example is wrong. "The optimizer is not allowed to change the program semantics" - of course it can, especially in multi threaded environment. You could add global variable and replace undefined behavior with global variable assignment and the program would still work differently depending on optimization levels.
I think while you are technically correct it isn't really meaningful. The optimizer isn't allow to change the guaranteed semantics of the program. The rule isn't that there is no observable difference between unoptimized and optimized because what would that even mean? What is the baseline? You could just as easily say that the optimized version is correct and the optimized one is "wrong"? As long as both versions don't break the semantics of the language there is nothing that would make the optimized version "more correct" than the other one. If my compiler puts a thousand nop instructions between every line of code that would also "change program semantics" according to your strict definition, but no one would ague that it is an invalid compiler.
> If it is implementation-defined, it is no longer considered an error by the C++ standard
I do't understand why implementation defined can't be "throws exception/abort on overflow for debug build and something more efficient for release builds". Why do they need a new category for that?
your compiler might choose to make that the "implementation-defined behavior", but another compiler could choose to always ignore it and give you no easy means of diagnosis.
my understanding is that the proposal of "erroneous behavior" limits the scope of the consequences of triggering the behavior (like implementation-defined behavior), while still clearly marking it as erroneous and something to be diagnosed (like undefined behavior).
I think I understand the motivation, but I'm not sure it is thought through. Taking the signed integer example, would platform defined wrapping be suitable behavior in the setting of erroneously defined behavior? If not, that would make it a poor fit for integer overflow, adding a branch to most signed integer math ops in release builds is a non starter for C++ projects.
Regarding the diverse compiler point you made, that argument can be used to torpedo most ideas in this space. For example sanitizers started in clang, and now gcc and msvc have similar options, if something is a good idea, chances are the others will copy it. And relying on erroneously defined behavior (EB) to be a hard error, means relying on behavior that can differ across implementations and platforms which is problematic in itself. Plus, even relying on a hard error implies that EB has to be an error which is problematic for the reasons outlined above, or means it can't be used in the places you'd like to use it like signed integer overflow.
What new category? The article discusses the choice between having signed integer overflow as an implementation-defined and undefined behavior. And it says if it had been implementation-defined, it wouldn't be an error and hence UB sanitizer would not be able to catch it anymore.
With signed integer overflow being the UB, sanitizers are able to catch them regardless of the level of optimization you apply to your code. "Debug" or "release".
And what you propose, and what I think Rust does, is in no way better because "something more efficient" is ambiguous at its best. I wonder what would that be considering that is a condition that you certainly _want_ to catch. I think the reason why Rust did it this way (by panicking on the overflows in non-optimized builds) is because it does not have support for sanitizers (yet).
The new proposed category is "erroneous behavior", which allows for the sort of "this code is wrong so I refuse to compile" errors while also encouraging a sort of "I will at least treat this consistently if I compile it" behavior from the compiler. It remains to be seen whether or not the committee will accept any papers with this new category.
As others have pointed out, compilers are already free to do that if they wish. GCC has a flag to trap on signed integer overflow, [0] I believe Clang does too.
Another option is to use a library like SafeInt [1] or Boost Safe Numerics [2] which offer drop-in replacement types like safe<int>.
If you want to use raw unchecked integer types in release builds, you could do that using a typedef like my_int, in combination with a #if.
Yeah a lot of the badness of UB in C and C++ is stupid and has no place in the language. UB should be reserved for cases where there is no definable outcome, and everything else should be unspecified or implementation defined. Things like overflow, null sereferences, etc should not give the compiler the opportunity to go back in time and remove preceding branches (which is what the current interpretation of UB allows).
9 comments
[ 0.22 ms ] story [ 33.2 ms ] thread> If it is implementation-defined, it is no longer considered an error by the C++ standard
I do't understand why implementation defined can't be "throws exception/abort on overflow for debug build and something more efficient for release builds". Why do they need a new category for that?
my understanding is that the proposal of "erroneous behavior" limits the scope of the consequences of triggering the behavior (like implementation-defined behavior), while still clearly marking it as erroneous and something to be diagnosed (like undefined behavior).
Regarding the diverse compiler point you made, that argument can be used to torpedo most ideas in this space. For example sanitizers started in clang, and now gcc and msvc have similar options, if something is a good idea, chances are the others will copy it. And relying on erroneously defined behavior (EB) to be a hard error, means relying on behavior that can differ across implementations and platforms which is problematic in itself. Plus, even relying on a hard error implies that EB has to be an error which is problematic for the reasons outlined above, or means it can't be used in the places you'd like to use it like signed integer overflow.
With signed integer overflow being the UB, sanitizers are able to catch them regardless of the level of optimization you apply to your code. "Debug" or "release".
And what you propose, and what I think Rust does, is in no way better because "something more efficient" is ambiguous at its best. I wonder what would that be considering that is a condition that you certainly _want_ to catch. I think the reason why Rust did it this way (by panicking on the overflows in non-optimized builds) is because it does not have support for sanitizers (yet).
Another option is to use a library like SafeInt [1] or Boost Safe Numerics [2] which offer drop-in replacement types like safe<int>. If you want to use raw unchecked integer types in release builds, you could do that using a typedef like my_int, in combination with a #if.
[0] https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html , the -ftrapv flag
[1] https://github.com/dcleblanc/SafeInt
[2] https://www.boost.org/doc/libs/release/libs/safe_numerics/do...