Search code examples
c++for-loopsizeunsignedunsigned-integer

Weird std::string::size() in a for loop


The program will print "Entered the loop" if I use input.size() - 1 as the for loop condition.

std::string input;
input = {""};
int i = 0;
for (; i < input.size() - 1; ++i)
{
    cout << "Entered the loop" << endl;
}

However, if I pass the value of input.size() -1 to an integer (checksize):

std::string input;
input = {""};
int checksize = input.size() - 1;
int i = 0;
for (; i < checksize; ++i)
{
    cout << "Entered the loop" << endl;
}

then the program will not go into the loop and will not print "Entered the loop"

I was wondering why this happen? It seems the two pieces of code are the same to me.


Solution

  • You are a victim of unsigned integers :)

    std::string::size() returns an unsigned integer (of type equivalent to size_t).

    When the compiler evaluates input.size() - 1, this kind of becomes size_t(0) - 1, and since the computation is done with unsigned integers, instead of -1 you get a very big integer number (MSVC 32-bit compiler prints 4294967295, which corresponds to the maximum 32-bit unsigned integer value 2^32 - 1).

    So this loop:

    for (int i = 0; i < input.size() - 1; ++i)
    

    is kind of equivalent to:

    for (int i = 0; i < /* very big positive number */; ++i)
    

    which will print your message many times.

    Instead, in the second case, when you evaluate input.size() - 1 and then assign it to an int variable (which is signed by default), the compiler still computes size_t(0) - 1 as a very big positive integer number, but then this number is converted to a (signed) int, resulting in checksize being initialized with -1, and your loop is never executed:

    for (int i = 0; i < checksize /* -1 */; ++i)
    

    Considering this compilable code:

    #include <iostream>
    #include <string>
    using namespace std;
    
    int main() 
    {
        string input;
    
    #ifdef CASE1
        for (int i = 0; i < input.size() - 1; ++i)
        {
            cout << "Entered the loop\n";
        }
    #else
        cout << "input.size() - 1  == " << (input.size() - 1) << '\n';
        cout << "SIZE_MAX          == " << SIZE_MAX << '\n';
    
        int checkSize = input.size() - 1;
        cout << "checkSize == " << checkSize << '\n';
    
        for (int i = 0; i < checkSize; ++i)
        {
            cout << "Entered the loop\n";
        }
    #endif
    }
    

    If you compile its CASE1 with MSVC and /W4 (warning level 4, which I highly suggest), you get a warning for your for loop condition:

    cl /EHsc /W4 /nologo /DCASE1 test.cpp
    
    test.cpp(10) : warning C4018: '<' : signed/unsigned mismatch
    

    which usually points to you that something is wrong with your code.

    Instead, compiling without CASE1, gives no warnings and the following output (which shows that the for loop's body is never executed):

    cl /EHsc /W4 /nologo test.cpp
    
    input.size() - 1  == 4294967295
    SIZE_MAX          == 4294967295
    checkSize == -1