-1

The scanf() is certainly a function that has many problems, so I've been looking for a way to substitute it.
I found this code in the first answer of this post, but even this code is bugged (the loop end if the user enter a newline). There's a way to get an integer in input without encountering any types of bug?

Code:

char *end;
char buf[LINE_MAX];

do {
     if (!fgets(buf, sizeof buf, stdin))
        break;

     // remove \n
     buf[strlen(buf) - 1] = 0;

     int n = strtol(buf, &end, 10);
} while (end != buf + strlen(buf));

EDIT:

I wrote this function based on the answer of @chqrlie and on a snippet of code that I found [here][2], is there any way to improve the function and make it better?
int readInteger(const char *prompt, const char *error) {
    int number;
    char buf[1024]; // use 1KiB to be sure
    
    while(1) {
        printf("%s", prompt);
        if(!fgets(buf, 1024, stdin)) exit(1);  // reading input failed
        
        // have some input, convert it to integer
        char *endptr;
        errno = 0;
        number = (int)strtol(buf, &endptr, 10);
        
        while(isspace((unsigned char)*endptr)) endptr++; // ignore trailing white space

        if(errno == ERANGE) printf("%s", error);
        else if(endptr != buf && !(*endptr && *endptr != '\n')) return number;
    }
}
link23
  • 53
  • 7
  • 2
    `buf[strlen(buf) - 1] = 0;` to `// remove \n` isn't safe. First, the input isn't guaranteed to end with a `\n` character, and if the input is an empty string (also possible) `buf[strlen(buf) - 1] = 0;` is undefined behavior. – Andrew Henle Feb 13 '21 at 16:04
  • 1
    You're right, that code is pretty terrible. See [this question](https://stackoverflow.com/questions/58403537/what-can-i-use-to-parse-input-instead-of-scanf) for some good answers describing how to read input reasonably reliably, without using `scanf`. – Steve Summit Feb 13 '21 at 16:05
  • 1
    You must test `if (endptr == buf) ...*` before skipping white space. Also change the last line to `if (*endptr == '\0') return number;` – chqrlie Feb 13 '21 at 17:36
  • Testing for `errno == ERANGE` is not sufficient as type `long` can have a greater range than type `int`. – chqrlie Feb 13 '21 at 17:40
  • After the `strtol()` call, you must check for successful conversion: if `endptr == buf` the conversion failed because the buffer does not contain a number. If `errno != 0` the number was beyond the range of type `long`. Note that `strtol` may return a number that is beyond the range of type `int` which you should test too. Skipping whitespace after the number is done in the `while(isspace(...))` loop and it also skips the newline character. If `*endptr != '\0'` there are extra non blank characters on the input line, which should also be considered an error. – chqrlie Feb 13 '21 at 19:14

1 Answers1

1

The posted code needs some improvements, but can be wrapped in a function to read an int value:

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

int get_int(const char *prompt, int *result) {
    char buf[100];
    char *end;

    for (;;) {
        if (prompt) {
            printf("%s: ", prompt);
        }
        if (!fgets(buf, sizeof buf, stdin)) {
            printf("unexpected end of file\n");
            return -1;
        }
        errno = 0;
        long n = strtol(buf, &end, 0);
        if (end == buf) {
            printf("invalid input: %s", buf);
            continue;
        }
        if (errno != 0 || n > INT_MAX || n < INT_MIN) {
            printf("value outside range of int: %s", buf);
            continue;
        }
        // ignore trailing white space
        while (isspace((unsigned char)*end) {
            end++;
        }
        if (*end != '\0') {
            printf("ignoring tailing input: %s", end);
        }
        *result = (int)n;
        return 0;   // success
    }
}
chqrlie
  • 114,102
  • 10
  • 108
  • 170