Does while loop contain undefined behavior because of integer underflow?
First off this is a Boolean Conversion:
A prvalue of integral, floating-point, unscoped enumeration, pointer, and pointer-to-member types can be converted to a prvalue of type bool
.
The value zero (for integral, floating-point, and unscoped enumeration) and the null pointer and the null pointer-to-member values become false
. All other values become true
.
So there will be no integer underflow if i
is properly initialized, it will reach 0U
and that will be cast to a false
value.
So what the compiler is effectively doing here is: while(static_cast<bool>(i--))
Do compilers have right to assume that while loop condition is always true
because of that and end up with endless loop?
The key reason this isn't an endless loop is that the postfix decrement operator returns the value of the decremented variable. It's defined as T operator--(T&, int)
And as discussed in previously the compiler is going to evaluate whether that value is 0U
as part of the conversion to bool
.
What the compiler is effectively doing here is: while(0 != operator--(i, 1))
In your update you cite this code as a motivation for your question:
void fn(void)
{
/* write something after this comment so that the program output is 10 */
int a[1] = {0};
int j = 0;
while(a[j] != 5) ++j; /* Search stack until you find 5 */
a[j] = 10; /* Overwrite it with 10 */
/* write something before this comment */
}
Upon inspection, that entire program has undefined behavior there is only 1 element of a
and it's initialized to 0. So for any index other than 0
, a[j]
is looking off the end of the array. The will continue till a 5
is found or until the OS errors because the program has read from protected memory. This is unlike your loops condition which will exit when the postfix decrement operator returns a value of 0, so there can't be an assumption that this is always true
, or that the loop will go on infinitely.
What if i
is signed int
?
Both of the above conversions are built-in to C++. And they are both defined for all integral types, signed and unsigned. So ultimately this expands to:
while(static_cast<bool>(operator--(i, 1)))
Doesn't it contain pitfalls related to array access?
Again this is calling a built-in operator, the subscript operator: T& operator[](T*, std::ptrdiff_t)
So a[i]
is the equivalent of calling operator(a, static_cast<ptrdiff_t>(i))
So the obvious followup question is what's a ptrdiff_t
? It's a implementation defined integer, but as such each implementation of the standard is responsible for defining conversions to and from this type, so i
will cast correctly.