DEV Community

Calin Baenen
Calin Baenen

Posted on

Interesting C++ bug involving `std::cin` for input.

So, I was making hangman for fun, and I had

unsigned short guesses;
std::string    word;

std::cin << guesses;
// ...
Enter fullscreen mode Exit fullscreen mode

And I discovered something interesting.
I was just playing around, and I typed 1, and spammed a bunch of 0s.
Sort of like the following.

1000000000000000000000
Enter fullscreen mode Exit fullscreen mode

And then it did something really strange, it skipped the next std::cin command (which was the one for word).
And then... it kept repeating, printing things over and over (probably because I was in a while(true)), but it didn't bother to stop when it hit std::cin, even on the next loop iteration, instead constantly having the number be equal to 65535, (2^16)-1; or in other words, the 16 bit integer (unsigned short) limit.

Even if I enter 65536, the unsigned short limit plus one, it still bugs out in this crazy way.

Here's an image of what happens after I do this:
Before submitting my answer of  raw `65536` endraw .
Literally only a second after I submit my answer of  raw `65536` endraw .

This is what should happen normally:
This is what should happen.

Strangely, this bug will also happen if I pass any strings that include a space () character in them for answer (for some reason).

This is my code:

int main() {
  while(true) {
    unsigned short guesses;
    std::string    answer;

    std::cout << "Enter the amount of guesses to allow: ";
    std::cin >> guesses;
    std::cout << "Enter the word for P2 to guess: ";
    std::cin >> answer;

    std::cout << "[Guesses: " << guesses << ", " << "Word: \""
      << answer << "\"]\n";
  }
}
Enter fullscreen mode Exit fullscreen mode

If anyone knows what's causing these bugs, and how I can fix them, let me know!

Thanks!
Cheers!

Discussion (5)

Collapse
pgradot profile image
Pierre Gradot

Obviously, 65536 can't fit in an unsigned short and the convertion fails. cppreference explains why you get 65535:

For unsigned integers, if extraction results in the value too large or too small to fit in value, std::numeric_limits<T>::max() is written and failbit flag is set.

You should call fail() to check whether the conversion has been sucessful. You can also test the stream with operator!().

If the conversion has failed, you should clear() the error state flags. You should also empty the input buffer, otherwise the next iteration of the loop will run into the same bad convertion [repeat while true].

The code may then looks like:

#include <iostream>

int main() {
  while (true) {
    unsigned short guesses;
    std::cout << "Enter the amount of guesses to allow: ";
    std::cin >> guesses;

    if (std::cin.fail()) {
      std::cout << "Conversion failed" << '\n';
      std::cin.clear();
      std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    } else {
      std::cout << "You have entered: " << guesses << '\n';
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
greenroommate profile image
Haris Secic

The space is parameter separator so I'm not supprised that it triggers input in the next loop iteration. What I am supprised is that buffer is not getting emptied on next iteration causing it to read stuff over and over again. But I'm used to garbage collected languages so I might be expecting more than I should. Anyways try clearing the input buffer on last line of loop, see what happens. I would assume that a single unput input of 100000000000 would overflow uint couple of times and on next interation it would accept new input. Would also expect that empty input (when you hit space after answer) for uint would be 0 or some garbage numeric value not overflowing the buffer again multiple times.

Collapse
baenencalin profile image
Calin Baenen Author

How do you clear it?
The ignore method? (Or is that slightly different than what I want?

Collapse
greenroommate profile image
Haris Secic • Edited on

I think that comment from @pgradot is the way to go. Check for failure, then clear the buffer, then ignore stuff, and finally do the rest - output messages and ask for inputs. Insted of clearing at the end as I suggested it's probably correct to first validate, clear, ignore.

So not just 'ignore' method but combination as described in mentioned comment links.

Collapse
baenencalin profile image
Calin Baenen Author

When I try std::getline(std::cin, answer), it doesn't work, it just acts as if there was no call made to getline, or to std::cin.