Home > Back-end >  Why does this loop run?
Why does this loop run?

Time:03-17

#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main()
{
std::string qaz{};
vector <size_t> index ;
cout <<"qaz: "<<qaz<<" length: "<<qaz.length()<<"\n";  
for (size_t i{0}; i<= ( qaz.length()-2);i   )
{   cout<<"Entered"<<i<<"\n";      
    cout<<"Exited"<<i<<"\n";}

return 0;
}

//Here qaz is an empty string so qaz.length() == 0 (so qaz.length()-2 == -2) and i is initialized to 0 so I expected that we will not enter the loop. But on running it I find that it goes on in an infinite loop. Why? Please help me with it.

CodePudding user response:

See docs for size_t:

std::size_t is the unsigned integer type of the result of the sizeof operator

(Emphasis mine.)

Furthermore, string::length returns a size_t too1.

But even if that were not the case, when comparing signed values to unsigned values, the signed value is converted to unsigned before the comparison, as explained in this answer.

(size_t)0 - 2 will underflow as size_t is unsigned and therefore its minimum value is zero resulting in a large number which is usually2 either 232-2 or 264-2 depending on the processor architecture. Let's go with the latter, then you will get 18,446,744,073,709,552,000 as result.

Now, looking at the result of 0 <= 18446744073709552000 you can see that zero is clearly less than or equal to 18.4 quintillion, so the loop condition is fulfilled. In fact the loop is not infinite, it will loop exactly 18,446,744,073,709,552,001 times, but it's true you will probably not want to wait for it to finally reach its finishing point.

The solution is to avoid the underflow by comparing i y <= x instead of i <= x - y3, i.e. i 2 <= qaz.length(). You will then have 2 <= 0 which is false.


1: Technically, it returns an std::allocator<char>::size_type but that is defined as std::size_t.

2: To be exact, it is SIZE_MAX - (2 - 1) i.e. SIZE_MAX - 1 (see limits). In terms of numeric value, it could also be 216-2 - such as on an ATmega328P microcontroller - or some other value, but on the architectures you get on desktop computers at the current point in time it's most likely one of the two I mentioned. It depends on the width of the std::size_t type. If it's X bits wide, you'd get 2X-n for (size_t)0 - n for 0<n<2X. Since C 11 it is however guaranteed that std::size_t is no less than 16 bits wide.

3: However, in the unlikely case that your length is very large, specifically at least the number calculated above with 2X-2 or larger, this would result in an overflow instead. But in that case your whole logic would be flawed and you'd need a different approach. I think this can't be the case anyway because std::ssize support means that string lengths would have to have one unused bit to be repurposed as sign bit, but I think this answer went down various rabbit holes far enough already.

CodePudding user response:

length() returns unsigned value, which cannot be below zero. 0u - 2 wraps around and becomes very large number.

Use i 2 <= qaz.length() instead.

CodePudding user response:

The issue is that size_t is unsigned. length() returns the strings size_type which is unsigned and most likely also size_t. When the strings size is <2 then length() -2 wraps around to yield a large unsigned value.

Since C 20 there is std::ssize which returns a signed value. Though you also have to adjust the type of i to get correct number of iterations also when i < -2 is the condition:

#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main()
{
    std::string qaz{};
    vector <size_t> index ;
    cout <<"qaz: "<<qaz<<" length: "<<qaz.length()<<"\n";  
    for (int i{0}; i<= ( std::ssize(qaz)-2);i   )
    {   
        cout<<"Entered"<<i<<"\n";      
        cout<<"Exited"<<i<<"\n";
    }
}

Alternatively stay with unsigneds and use i 2 <= qaz.length().

  •  Tags:  
  • c
  • Related