Search code examples
cfgetsscanf

C fgets and sscanf in loop : prevent useless looping


I have a problem with looping and fgets and sscanf to get the input.

I know to problem is from the size of the malloc with the input. If the user enter number larger than the malloc i want to ask again to enter a new number. But in this code, if an user enter a too large number, it's looping lot of time (size of the word / 8) i think.

How to ask again to the user to enter new number without looping 4 times for example. See the example i made with big number.

The idea was to free the input after the loop but it's doesn't works. Any ideas ?

There is my code :

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>


int main(void) {
    char x[8];
    char y[8];
    int result = 0;
    char *input=malloc(sizeof(char)*8);
    bool answer = 0;
    char *pos;

    while(!answer) {

        fgets(input, sizeof(input)-1, stdin);
        //remove the /n from fgets
        if ((pos=strchr(input, '\n')) != NULL)
            *pos = '\0';

        result = sscanf (input, "%s %s", x, y);
        printf("%d\n", result);
        if(result < 2) {
            fprintf(stderr, "There is an error with the number you give, try again\n");
        } else {
            printf("%s\n", x);
            printf("%s\n", y);
        }

    }
    return 0;
}

And the output for : "01 01"

01 01
2
01
01

Output for : 000000005 000000005

0000000000005 000000000000005
1
There is an error with the number you give, try again
1
There is an error with the number you give, try again
2
5
0000
1
There is an error with the number you give, try again
1
There is an error with the number you give, try again

Solution

  • fgets() doesn't throw away the rest of the line when it's longer than its buffer. You have to do it yourself.

    If you look at this code I frequently use with fgets, you'll see the two tasks separated, and in which circumstances which one is done:

    /*Returns 0 if OK, a negative value if EOF.*/
    int fpurge(FILE *f)
    {
        int c;
        while((c=fgetc(f))!=EOF && c!='\n')
        { }
        return (c==EOF ? -1 : 0);
    }
    
    /* Returns a nonzero value if found, zero if not. */
    int truncate_newline(char *str)
    {
        int bRet=0;
        if(str!=NULL)
        {
            char *pNewLine = strchr(str, '\n');
            if(pNewLine!=NULL)
            {
                bRet = 1;
                *pNewLine = '\0';
            }
        }
        return bRet;
    }
    
    /* Returns 0 if buffer is full, a positive value if line is complete,
       a negative value if EOF (implies buffer full). */
    int fclean(char *str, FILE *f)
    {
        int ret = 1;
        if(!truncate_newline(str))
            ret = fpurge(f);
        return ret;
    }
    

    You can see that your own code does the truncate_newline part, but not the "throw away the rest of the line" (here in the function fpurge) part.

    If you change your code thusly, it should work:

    #include <stdio.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <getopt.h>
    #include <stdbool.h>
    #include <string.h>
    
    #define BUFFER_SIZE 8
    
    int main(void) {
        char x[BUFFER_SIZE];
        char y[BUFFER_SIZE];
        int result = 0;
        char *input=calloc(BUFFER_SIZE, sizeof(char));
        bool answer = 0;
        char *pos;
    
        while(!answer) {
    
            fgets(input, BUFFER_SIZE, stdin);
            //remove the /n from fgets
            if ((pos=strchr(input, '\n')) != NULL)
                *pos = '\0';
            else
            {
                int c;
                while((c=getchar())!='\n' && c!=EOF) {}
            }
    
            result = sscanf (input, "%s %s", x, y);
            printf("%d\n", result);
            if(result < 2) {
                fprintf(stderr, "There is an error with the number you give, try again\n");
            } else {
                printf("%s\n", x);
                printf("%s\n", y);
            }
    
        }
        return 0;
    }
    

    Or simply replace the whole if() with fclean(input, stdin);