1

How can I get a safe input of integer (especially, positive number) using scanf or gets? I've tried several solutions and each solution had some problems.

1. Using getchar() to remove string inputs

int safeInput() {
    int input;
    scanf("%d", &input);
    while(getchar() != '\n');
    return input;
}

This method effectively handles string inputs, however, if strings such as 3a are inputted, the value of input becomes 3, which is not a true exception handle.

2. Retrieving input as a string then converting to integer value.

int safeInput() {
    char[200] input, safe_input;
    gets(input);
    // I know about the security issue about gets - but it's not the point.

    int i = 0;
    while (1) {
        if (input[i] >= 48 && input[i] <= 57) safe_input[i] = input[i];
        else break;
        i++;
    }

    return atoi(safe_input);
}

This method has problem that it cannot handle if string that has longer length than allocated to input was inputted.

3. What if defining a string using pointer?

I concerned about defining input by pointer, like char *input;. However, once I executed gets(input)(or scanf("%s", input)), it raised runtime-error.


So what is a proper way to retrieve an integer value from console window using scanf or gets?

GreenRoof
  • 140
  • 1
  • 12
  • 2
    Don't use `gets`. – jxh Apr 30 '18 at 08:51
  • 2
    Concerning the 3rd method: `char *input;` just declares a pointer, but `input` points nowhere, it's not initialized. Read the chapter dealing with pointers in your C text book. on't use `gets`. Use `fgets` instead. – Jabberwocky Apr 30 '18 at 08:52
  • So what's the problem with method 2? – Jabberwocky Apr 30 '18 at 08:55
  • BTW in method 2 you forgot to NUL terminate the string in `safe_input`. Read the chapter dealing with strings in your C text book. – Jabberwocky Apr 30 '18 at 09:01
  • 2
    1. read line-wise (`fgets()`), 2. parse the input (`strtol()` and friends). See also my [beginners' guide away from scanf](http://sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html). –  Apr 30 '18 at 09:01
  • @MichaelWalz An overflow input. I know about the NULL termination but I didn't add there it's not the point – GreenRoof Apr 30 '18 at 09:03
  • Does your system provide the POSIX `getline` function? – jxh Apr 30 '18 at 09:04
  • 1
    @GreenRoof well without the NUL terminator it's just totally wrong. And, yes an input overflow _is_ a problem if you use `gets`. It is not if you use `fgets`. – Jabberwocky Apr 30 '18 at 09:11
  • If `getline` is not available to you, you can write your own. https://stackoverflow.com/q/314401/315052 – jxh Apr 30 '18 at 09:14

2 Answers2

3

The answer depends on what exactly you mean by safe. If you want to catch any possible input error, your only option is to use a function of the strtol() family, which even allows for a range check. In my beginners' guide away from scanf(), I'm describing its use.

Here's the code adapted to what you're attempting here, with comments:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

// return success as boolean (0, 1), on success write result through *number:
int safeInput(int *number)
{
    long a;
    char buf[1024]; // use 1KiB just to be sure

    if (!fgets(buf, 1024, stdin))
    {
        // reading input failed:
        return 0;
    }

    // have some input, convert it to integer:
    char *endptr;

    errno = 0; // reset error number
    a = strtol(buf, &endptr, 10);
    if (errno == ERANGE)
    {
        // out of range for a long
        return 0;
    }
    if (endptr == buf)
    {
        // no character was read
        return 0;
    }
    if (*endptr && *endptr != '\n')
    {
        // *endptr is neither end of string nor newline,
        // so we didn't convert the *whole* input
        return 0;
    }
    if (a > INT_MAX || a < INT_MIN)
    {
        // result will not fit in an int
        return 0;
    }

    // write result through the pointer passed
    *number = (int) a;
    return 1;
}
0

First if you want a safe input, do not use gets. Saying that you know about the issues is not a true excuse when you could use fgets. Next, the trick is to try to read a non blank character after the int: if you find no one, then there is nothing after the int on the line.

int safeInput(int *input) {   // the return value is the indicator of failed read
    int c;
    char dummy[2];  // never forget the terminating null!
    if (scanf("%d%1s", input, dummy) == 1) return 1;
    // in case of error, skip anything up to end of line or end of file
    while (((c = fgetc(stdin)) != '\n') && (c != EOF));
    return 0;
}

The nice point here, is that when scanf returns 1, the %1s has eaten anything up to the end of line, including the terminating 'n'. But this has a major drawback: the scanf will only end on end of stream or after reading one additional (non blank) character. For that reason, Felix Palmen's answer is easier and safer to use.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • looks nice if you insist using `scanf()` -- shouldn't `%*1s` work as well, so you don't need a dummy buffer? –  Apr 30 '18 at 09:14
  • There is another weakness to using `scanf` not mentioned in the question, which is it will overflow if the input is larger than what the type can hold. – jxh Apr 30 '18 at 09:20
  • 1
    The `%1s` trick doesn’t work. It skips over white space and demands something that’s not white space before it returns. It will eat all the newlines you type until you type something that’s not white space. – Jonathan Leffler Apr 30 '18 at 09:23
  • @JonathanLeffler: I think this question is largely a duplicate of https://stackoverflow.com/questions/26583717/how-to-scanf-only-integer but the question seems to be framed slightly differently, even though the answers are the same. – jxh Apr 30 '18 at 09:40
  • @JonathanLeffler: You are right, I have edited my post with that. – Serge Ballesta Apr 30 '18 at 09:51