0

i have a function that ask to the user to enter a value to calculate its square root, however when i try to validate that the number of the input must be a number and not a char, it makes an infinite loop

void opcion1(void){
    float A, K, i, aux;
    int awnser;
    ask:
    fflush( stdin );
        printf("enter the value for A: ");
        sleep(1);
        awnser = scanf("%f", &A);
    if(awnser < 1){ // not a number
        fputs("\nA is not a number\n", stderr);
        goto ask;
    }
    if(A < 0 ){
        aux = -A;
        i = sqrt(aux);
        if(A == (int)A) printf("\nthe square root of %.0f, is%.0fi", A, i);
        else printf("the square root of %.3f, is %.4fi", A, i);
    }else{
        K = sqrt(A);
        printf("the square root of A is %.2f", K);
    }
}

output:

enter the value for A:
k
A is not a number
enter the value for A:
A is not a number
enter the value for A:
A is not a number
enter the value for A:
A is not a number
enter the value for A:
Breakpoint
  • 428
  • 6
  • 19
  • you mean, if you enter `k` you infinitely get those two lines printed? – mangusta Oct 06 '19 at 04:08
  • 1
    The problem is `fflush(stdin)`. The C standard only allows `fflush` on output streams, not input streams. One way to solve the problem is to read a line with `fgets`, and then use `sscanf` to parse the number. – user3386109 Oct 06 '19 at 04:13
  • Have a look at this link. https://stackoverflow.com/a/1716066/9345287 – Breakpoint Oct 06 '19 at 04:18
  • Because `scanf()` leaves the character that wasn't accepted in the input buffer, if you try to read a number and there is a non-number (letter or punctuation, for example) in the input buffer, you get an infinite loop. Test whether `scanf()` succeeded. If not, the usual trick is to use a loop like `{ int c; while ((c = getchar()) != EOF && c != '\n') ; }` (so there's an empty loop body). You shell should probably exit if `scanf()` reports EOF. – Jonathan Leffler Oct 06 '19 at 05:15

1 Answers1

1

The main issue is that its hard to understand all these concepts when you are just starting with the language. Like where the hell does scanf take the input? What is buffering?

I won't talk about technical terms, you can always google them if you want to go deep, will try to keep it simple. Also I am just talking from unix side and donno anything about windows.

scanf(3) calls read(2) internally and printf(3) calls write(2). So why don't we use read(2) and write(2) directly? The issue is that read(2)/write(2) are slow because for them a switch needs to be made to from user mode to kernel mode and this is a time taking process. What scanf() and printf() do is that they stay in user mode and collect the information there and read/write it in one go i.e. they store it in a buffer for time being.

Now this makes some more things possible. As the input hasn't reached your variable yet and is in the buffer, scanf() can do additional checks. Like in your case, check if the input is as per your format specifier. If not it won't assign it to your variable and return. But scanf won't clear that buffer where it received input. In your case in scanf(), when your input is not as per %f, it returns 0 and your if condition makes it go up and input again. But it never cleared the buffer. So the buffer contains the same input as from before and it acts again on it causing the same problem again. This continues infinitely.

Now to the solution:

This is a little confusing due to "standards" issue.

When you see the manpage of fflush(3),

It states:

For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.

But then at the bottom of the page,

The standards do not specify the behavior for input streams. Most other implementations behave the same as Linux.

So fflush(stdin) doesn't seem to work like that on your system. (just as it doesn't on mine)

Best ways to achieve this have already been discussed in many questions:

How to solve your problem:

Scanf skips every other while loop in C

Better way:

Using fscanf() vs. fgets() and sscanf()

Another way is to change fflush(stdin) to fpurge(stdin)(ITS NOT PORTABLE AND NOT STANDARD).

From the manpage of fpurge(3) which I don't see being discussed anywhere:

The function fpurge() clears the buffers of the given stream. For output streams this discards any unwritten output. For input streams this discards any input read from the underlying object but not yet obtained via getc(3); this includes any text pushed back via ungetc(3).

Also have a look at why its not a good idea to use goto in such scenarios. What is wrong with using goto?

Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39