Is there any performance penalty to unsigned integer underflow?
No.
There is no cost to the wrapping (modulo) behavior of arithmetic with unsigned integral types. Processors use same instructions for integer subtraction regardless of whether the values are considered signed or unsigned. So there's no performance difference of that subtraction based on the signedness of the operands.
(For some bitshift operations, different instructions are used for signed and unsigned types, but I'd be surprised if they ran at different speeds.)
Both C and C++ agree that unsigned integer arithmetic is well-defined for overflow and underflow. When the programmer chooses to use unsigned integral types for the operands, they are choosing the wraparound behavior. Neither the processor nor the compiler has to do any extra work to produce the correct result.
With signed integers, however, the C and C++ standards say the behavior of subtraction is undefined if an underflow occurs (even though we know what the processor will do!). Declaring it undefined frees the compiler to apply certain optimizations and/or to help catch bugs.
At first glance, the expression x - 42 < x
(where x
and 42
are signed integral types) seems like it should always be true. But it's not because x - 42
will underflow for certain values of x
. Maybe the programmer knows it'll never happen because x
will always be larger than 42. Or maybe they forgot to consider the possibility of underflow.
If the standard defined what the program's behavior must be in that case, the compiler would have to generate the instructions to do the subtraction and the comparison and possibly more to get the behavior aligned with what the standard required. And since the programmer's intent is unclear, what behavior should the standard require here?
Instead, the standard leaves it undefined. Any program where that subtraction can underflow is invalid. The compiler doesn't have any obligation to invalid programs, so it can assume that the underflow will never ever happen. And thus it can optimize away the entire expression.
Being able to detect whether an operation would over- or underflow could be useful. (Ironically, it's tricky to do correctly without over- or underflowing.) And a correct test could be costly in terms of performance.
It might be relatively inexpensive to have the compiler, when targeting certain processors, insert instructions to test for overflow and underflow immediately after it occurs. (Remember, the behavior is well-defined for the processor, just not the language.) Thus the compiler could make your code trap when the problem occurs. Like dereferencing an invalid pointer, a crash is likely better than the program proceeding in a bad state. I believe clang's undefined behavior sanitizer (UBSan) does that.