4

I am learning C and I am trying to use fgets() and strtol() to accept user input and just take the first character. I am going to make a menu that will allow a user to select options 1-3 and 4 to exit. I want each option to only be selected if '1', '2', '3', or '4' are selected. I don't want 'asdfasdf' to work. I also don't want '11212' to select the first option since it starts with a 1. I created this code so far while I started testing and for some reason this loops over the question and supplies a 0 to the input.

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

int main() {
    char a[2];
    long b;

    while(1) {
        printf("Enter a number: ");

        fgets(a, 2, stdin);

        b = strtol(a, NULL, 10);

        printf("b: %d\n", b);
    }
  return 0;
}

output

Enter a number: 3
b: 3
Enter a number: b: 0
Enter a number:

It should be:

Enter a number: 3
b: 3
Enter a number: 9
b: 9
bob dilly
  • 51
  • 1
  • 3
  • You may want to visit [call menu from another function](https://stackoverflow.com/questions/49397889/call-menu-function-in-another-function/49398972#49398972) for hints on implementing your menu. – David C. Rankin Mar 24 '18 at 01:57

1 Answers1

2

You need to have enough room for the '\n' to be read or else it will be left in the input buffer and the next iteration it will be read immediately and thus make fgets() return with an empty string and hence strtol() returns 0.

Read fgets()'s documentation, it reads until a '\n' or untill the buffer is full. So the first time, it stops because it has no more room to store characters, and then the second time it still has to read '\n' and it stops immediately.

A possible solution is to increase the buffer size, so that the '\n' is read and stored in it.

Another solution, is to read all remaining characters after fgets().

The second solution could be cleanly implemented by reading one character at a time instead, since you are just interested in the first character you can discard anything else

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

int main() 
{
    int chr;
    while (1) {
        // Read the next character in the input buffer
        chr = fgetc(stdin);
        // Check if the value is in range
        if ((chr >= '0') && (chr <= '9')) {
            int value;
            // Compute the corresponding integer
            value = chr - '0';
            fprintf(stdout, "value: %d\n", value);
        } else {
            fprintf(stderr, "unexpected character: %c\n", chr);
        }
        // Remove remaining characters from the
        // input buffer.
        while (((chr = fgetc(stdin)) != '\n') && (chr != EOF))
            ;
    }
    return 0;
}
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • Ah thank you. I changed it from a 2 to a 3 and now it works. So it should be taking '3', '\n', '\0' now. But if char a[2]; only holds 2, how does this fit? – bob dilly Mar 24 '18 at 01:37
  • A string in C must always contain an ending `'\0'` (*nul-terminating character*), or it isn't a string, it's just an array of characters. All *line-oriented* input functions (such as `fgets`) will attempt to read-and-include the trailing `'\n'` in the buffer they fill (if there is room). If there is not room, any unread characters remain in the input buffer (e.g. `stdin`) just waiting to be read on your next attempt. – David C. Rankin Mar 24 '18 at 01:48
  • @bobdilly And... don't skimp on buffer size! `char a[512] = "";` or `1024` doesn't hurt (you have roughly a 4Megabyte stack to work with). I'd rather be `10,000` bytes too long that `1` byte too short. Be reasonable, but don't skimp. And always check whether the last character read by `fgets` was the `'\n'` or you know characters remain unread. E.g., `#include ` and test `if (a[strlen(a) - 1] != '\n') { /* line too long, handle error */ }`. – David C. Rankin Mar 24 '18 at 02:08