Additionally, some compilers can insert code to detect signed integer overflow at runtime. Clang has the -fsanitize=undefined flag [1].
[1] https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
Portable means being defined in the ISO C standard.
The ISO C standard is hopelessly behind for decades. They don't even define a proper (unicode) string API (search, norm, utf8, fc) , constexpr, a memory model, ... and leave everything to the implementors.
Additionally, being available doesn't mean it is the compiler one is allowed to use.
Apparently you haven't kept up with ISO C, C11 defined the memory model and portable threads. It is unfortunate that compiler vendors, specially your beloved gcc and clang disregard the security Annex K.
Such as? Do not get me wrong, I am against using GNU extensions, for example, and I do tend to make my code as portable as possible, but my main issue was with people glorifying the standard to what compilers themselves do not even strictly conform, and sometimes for good reasons. When you write code, you cannot ignore the compiler for obvious practical reasons. It does not matter what the paper says when the compiler does something completely different, or differently. If something goes terrible, you cannot say, "Oops, I was silly. I did not bother checking the documentation of the compiler.".
This reminds me of something like: https://lkml.org/lkml/2018/6/5/769
[1] https://github.com/openbsd/src/blob/master/lib/libc/stdlib/r...
Mozilla has a CheckedInt (C++) type that checks all operations for overflow. You can see examples of how this is actually implemented here: <https://searchfox.org/mozilla-central/source/mfbt/CheckedInt.... You can derive similar C implementations if you work through all of the template magic.
Be warned that the definition of what is undefined, implementation-specific, and well-defined varies depending on the exact version of the C or C++ standard you use.
I get very tired of people trying to make this "really" about "undefined behavior" sanity when real compilers are real software with real behavior. And here there is a clearly desired behavior (whether or not it got written into a standard somehwere), and clang implemented something else. That's wrong.
The most popular architecture today is 64-bit, with ABI specifying that the integer type is 32-bits wide.
So when faced with performing an operation on two 32-bit signed integers, on a 64-bit platform, you have to either:
1) perform the operation in 32-bit registers - if available - and even if available, usually far far far fewer in number thus massive performance & resource penalties to code
2) perform the operation in 64-bit registers, then add code to check the result, and if it would overflow in 32-bit, compute what the overflowed result would have looked like in a 32-bit register, and return that instead - again quite a performance hit
3) perform the operation in 64-bit registers, and just return the lower 32-bits, whatever that might be
The standard declined to pick a choice, which is why we have UB here.
(For unsigned integers, you can just truncate the result and it works every time, which is why the standard defines the result)
False for x86-64. There are sixteen 32-bit general-purpose registers: eax, ebx, ..., r8d, ..., r16d. There are sixteen 64-bit registers too: rax, rbx, ..., r8, ..., r16.
Here's an example of what can happen if the compiler doesn't perform these optimizations: https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759...
The loop codegen when the optimization is not performed is pretty atrocious. That is the bug, and solving that bug is the reason why compilers assume signed overflow cannot happen.
If you want to blame something, blame the C language. Compiler developers are just doing their jobs.
That's a great article, although I don't think it shows the advantage of assuming that undefined behavior will never occur. Rather than assuming "no undefined behavior", wouldn't it be legal (and preferable) for a compiler to just let the overflow (or lack of overflow) occur, and make the same optimization? For me the entire point of contention is the difference between "undefined" and "implementation defined". Maybe I'm wrong about the details here, but I think this is a case where a compiler could be just as fast without the nasal daemons.
So it's not always the case that the compiler assumes "overflow won't happen" and then _removes code_ - it just compiles code in a way that is ideal for the non overflow case and then what happens in the overflow case just falls out of the generated assembly (usually it does something bad like OOB access, but the same would generally be true of a wrapping overflow also).
You can't make this "implementation defined though", because the behavior is from a C-source point of view, it is effectively: "In the case an int overflows, we silently add 32 more bits and keep incrementing". Obviously you can't support that behavior in general, and even in specific cases it leads to nonsense like ints have non-representable values, etc.
Compiler developers are doing a fantastic job, and I absolutely do blame the C language and standard.
For the cases referenced in the article, it seems to me that the preferred solutions would be making int 64-bits and leaning on the restrict keyword rather than the semi-disaster that is strict aliasing (https://blog.regehr.org/archives/1307).
I'm surprised to see that for unsigned ints modular semantics are guaranteed by the spec
"Please tell me again how unsigned ints are broken" It seems this meme seems to be the C version of anti-vaxxers.
Basically, a great tool for static analysis and/or Frama-C for just the range analysis. Then, a coding style that facilitates such analysis. That should cover analysis. Idiomatic might be a problem because I see so many overflows in C code that it seems idiomatic for it to have them. ;)