GIDNetwork > Things to Avoid in C/C++ -- scanf / number, Part 8
Register
« Things to Avoid in C/C++ -- scanf / string, Part 7 Things to Avoid in C/C++ -- scanf / epilogue, Part 9 »

Things to Avoid in C/C++ -- scanf / number, Part 8

by: WaltP - Sep 28, 2005

scanf() / Reading a number

This is where scanf() actually doesn't do a bad job. Not great, but not bad either. What it does right is read the entire value. It ignores white space and new-lines too. What it does wrong is mess up if an illegal character is entered. But to be fair, it does tell you about it. Usually. Most programmers just don't listen.

Like the other formats, when scanf() reads a number it stops the moment it sees a non-number. This still leaves trailing spaces and new lines in the input stream. But on the next read the space and new lines are read off before the number is reached. This is a good thing.

Create the following program (mine is called scanf-n1.c):

C/CPP/C++ Code Example:

#include <stdio.h>
int main()
{
    int v;  // value read from scanf
    int r;  // return value from scanf
    int e;  // error count
    e = 0;  // initialize to no errors
    do
    {
        r = scanf("%d", &v);   
        if (!r) e++;        // if error, count it
        printf("        %3d Read, return %d \n", v, r);
    } while ((v != 0) && (e < 4));
    return 0;
}

The program (loop) exits when either a 0 is read or 4 errors have been counted. scanf() also returns an integer value that shows how many values have been converted. So start entering values:

Generic Code Example:

D:\GIDForums>scanf-n1
2
          2 Read, return 1
45
         45 Read, return 1
8 7 34 510
          8 Read, return 1
          7 Read, return 1
         34 Read, return 1
        510 Read, return 1
43 0
         43 Read, return 1
          0 Read, return 1

D:\GIDForums>

This shows that scanf() is actually behaving! So let's give it a little trouble. Note that we are reading an integer. Let's enter a float:

Generic Code Example:

D:\GIDForums>scanf-n1
23 65.3 17 4.2
         23 Read, return 1
         65 Read, return 1
         65 Read, return 0
         65 Read, return 0
         65 Read, return 0
         65 Read, return 0

D:\GIDForums>

Hmmm... It read the 23 and 65 fine. But notice its return code is 0 and started looping. That's because the decimal point in 65.3 is an invalid character for an int type. The decimal is left in the buffer and every read from that point on fails, leaving the old value in the variable. The decimal is never removed, and the value is perpetually the last value read. It is therefore necessary after every scanf() call that reads non-character data (ints, floats, etc.) to check the return status to see if it worked. If not, your input buffer is probably hosed. It would be difficult to fix (the problem could be 1 character or many) so your easiest solution is to abort the input with an error and have the user re-enter.

Let's change the program slightly:

C/CPP/C++ Code Example:

#include <stdio.h>
int main()
{
    int v1,v2,v3,v4; // values read from scanf
    int r;          // return value from scanf
    int e;         // error count
    e = 0;        // initialize to no errors
    do
    {
        r = scanf("%d %d %d %d", &v1, &v2, &v3, &v4);   
        if (r != 4) e++;        // if error, count it
        printf("        %3d %3d %3d %3d Read, return %d \n\n", 
                         v1, v2, v3, v4, r);
    } while ((v1*v2*v3*v4 != 0) && (e < 4));
    return 0;
}

This version reads 4 ints before continuing, and if any of them are 0 the program exits:

Generic Code Example:

D:\GIDForums>scanf-n2
1 2 3 4
          1   2   3   4 Read, return 4
          
23 34 45 56
         23  34  45  56 Read, return 4
         
23 45
65
76
         23  45  65  76 Read, return 4
         
23 5 0 7
         23   5   0   7 Read, return 4
         
D:\GIDForums>

Notice in the 3rd attempt the program just waits until all 4 values are entered? Also note the return value is 4. This value tells the programmer that 4 values have been read.

Generic Code Example:

D:\GIDForums>scanf-n2
23 65 23.1 9
         23  65  23 4259968 Read, return 3
         23  65  23 4259968 Read, return 0
         23  65  23 4259968 Read, return 0
         23  65  23 4259968 Read, return 0

D:\GIDForums>

In this test, the third value contains a decimal point. Up to the decimal was read and converted, then the error. This shows that only 3 values were read from the input buffer but the 4th was not. So 3 out of our 4 variables have data, the 4th has invalid data.

So at least scanf() will show you what it did, and let you know how far it went. But there's one last thing. What about this:

Generic Code Example:

D:\GIDForums>scanf-n2
23 98 54 67.5
         23  98  54  67 Read, return 4
         23  98  54  67 Read, return 0
         23  98  54  67 Read, return 0
         23  98  54  67 Read, return 0
         23  98  54  67 Read, return 0

D:\GIDForums>

The first read actually looks like it worked! We didn't see the error until we already processed 67.5 as 67 and our data is corrupt. So in this case, the return value is useless as an error indicator. And because after the first read the error is still hanging out in the input buffer (it's not in our program yet) we have no idea

  1. if there is a problem (until the next read)

  2. what the problem is (after the next read)


until we find some other way to read the rest of the buffer.

Based on these tests, I would say scanf() just failed as a viable input mechanism.

Most people would suggest using the fgets() / sscanf() team to alleviate these difficulties. Unfortunately, even though the input buffer stays clean, the conversion problems are the same. 67.5 will still be read as 67 and the return value may not give you a good indication of the error.

So individual parsing of the user's keyboard input is the only way to make your input bulletproof. You have to take over and use tried and true conversion functions to read numbers and not rely on scanf() to do the job.

The final frontier, scanf(): So what's a programmer to do? is up next...

See you soon.

Would you like to comment? This story has been viewed 74,476 times.
« Things to Avoid in C/C++ -- scanf / string, Part 7 Things to Avoid in C/C++ -- scanf / epilogue, Part 9 »

__top__

Copyright © GIDNetwork™ 2001 - 2024

Another website by J de Silva

Page generated in : 0.00931 sec.