Home > Back-end >  In C: how do I make sure user input is a single integer?
In C: how do I make sure user input is a single integer?

Time:10-06

(Homework)

This program takes an integer as input from the user, and displays that many numbers of the Fibonacci sequence (using a child process created in UNIX). For my assignment, the program also needs to perform error checking to ensure that the input is valid: The number of arguments should be correct and the given number should be a positive integer.

I'm not sure how to make verify that the number entered by the user isn't a decimal, or how to stop the user from entering multiple arguments separated by a space (ie: 1 12 7).

Please excuse me if this is a silly question, but this is my first time using C. Thank you for any help you can provide!

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int a = 0, b = 1, n = a   b, i; // decalre global variables and initialize some of them

    printf("Enter the number of a Fibonacci Sequence:\n"); // print message to the terminal asking user to enter a number 

    scanf("%d", &i); // (& is used to get the addresss of a variable) scan user input and store at the address of i (i = user input)
    
    if (i <= 0) {
        printf("please only enter an integer number greater than 0\n");
        return 1;
    }

    // check number of arguments 

    // check that a float was not entered 

    // printf("first (before pid_t): id = not yet declared, parent pid = %d\n", getpid()); // TEST

    // the return value from the fork system call: 
    // for the child, its value is 0. For the parent, it's the actual pid of the child
    pid_t id = fork(); // (pid_t integer type that can rep a processes ID) creates a child process from the original and sets id equal to the return value of the fork 

    // printf("second (after pid_t, in child process): id = %d, pid = %d\n", id, getpid()); // TEST

    if (id < 0) { // if id < 0 an error occured 
        fprintf(stderr, "Fork Failed"); // stderr is the standard error message to print output to terminal. fprintf is the format print
        return 1; // retrun 1 to exit the program 
    }

    if (id == 0) // if id == 0 then we know this is the child process
    {
        //printf("third (in child, after testing id value): child id = %d, pid = %d\n", id, getpid()); // TEST 

        printf("child: the first %d numbers in the fibonnaci sequence are:\n", i); // print a message with the number entered by the user
        
        if (i == 1) {
            printf("%d", 0);
            return 0;
        }
        else {
            printf("%d %d", 0, 1);
            i -= 2;
        }

        while (i > 0) {
            n = a   b;
            printf(" %d", n);
            a = b;
            b = n;
            i--;
        }
    }
    else // if cpu goes back to parnet before child has completed, tell parent to wait until child has completed
    {
        printf("Parent is waiting for child to complete...\n");

        waitpid(id, NULL, 0); // suspends the calling process until the child pricess ends or is stopped. First parameter is the pid to wait for, the others aren't relevant here 

        printf("\nParent: the child process is done\n");

        //printf("fourth (in else clause): id = %d, pid = %d\n", id, getpid()); // TEST
    } 

    return 0; // program has finished and exited without error. Main must return an int
}

CodePudding user response:

First you might want to clearly define what sorts of inputs you do/don't want to disallow. Here's a picture illustrating them all:

"xxx   -123456789012.345e67   yyy\n"
  ^  ^ ^    ^     ^   ^  ^  ^  ^  ^
  |  | |    |     |   |  |  |  |  |
  |  | |    |     |   |  |  |  |   -- trailing \n
  |  | |    |     |   |  |  |   ----- trailing non-numeric garbage
  |  | |    |     |   |  |   -------- trailing whitespace
  |  | |    |     |   |   ----------- exponent
  |  | |    |     |    -------------- fractional part
  |  | |    |      ------------------ more digits than fit in 32-bit int
  |  | |     ------------------------ normal integer
  |  |  ----------------------------- sign
  |   ------------------------------- leading whitespace
   ---------------------------------- leading non-numeric garbage

For a complete solution, for each of those parts, you have to decide whether to accept them as legal, or reject them as illegal, or quietly ignore them as "don't care", or hope they don't happen.

scanf can take care of three or four of those. strtol can take care of most of them, or almost all of them if you do a little more work on the side, or all of them if you do significantly more work on the side.

(I assume you're only interested in decimal or base-10 input. If you want to read hexadecimal or binary, you also have to decide whether you want to accept a leading 0x or 0b.)

[Disclaimer: Yes, this was basically an extended comment, not an answer.]

CodePudding user response:

scanf returns the number of items successfully read and assigned. So the first step is to check the return value of scanf:

if ( scanf( "%d", &i ) == 1 )
  // process i
else
  // bad input

We test against 1 because we're attempting to read one item.

The %d specifier tells scanf to skip over any leading whitespace, then read up to the next non-digit character. That means it will reject inputs beginning with non-digits like abc, a23, .333, etc.; for these inputs, it will return 0 and not write anything to i.

But...

For inputs like 12.3, it will convert and assign the 12 to i and return 1, leaving the .3 in the input stream to foul up the next read.

Furthermore, if you try to read another input with %d, it will immediately fail because of the leading . in .3; if you don't somehow remove that offending character from the input stream, you'll never read past it with %d.

scanf's great when you know your input will always be well-behaved, which means it's not a good tool for interactive input.

My preferred method is to read the input as text, then try to convert it using strtol/strtoul (for integer types) or strtod (for floating point types). Quick, dirty, untested example:

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

#define BUFSIZ 128

/**
 * Reads the next integer from the specified stream and stores
 * it to *var.  Returns 1 (true) for success, 0 (false) for failure.  
 * If we return 0, the contents of *var are unchanged.
 */
int getNextInt( FILE *stream, int *var )
{
  char inbuf[BUFSIZ 1] = {0};
  
  /**
   * Read input as text
   */
  if ( fgets( inbuf, sizeof inbuf, stream ) )
  {
    /**
     * Look for a newline - if one isn't present, 
     * the input was too long for our buffer.  We
     * reject the input out of hand, but first we
     * need to consume the extra characters including
     * the newline.
     */
    char *newline = strchr( inbuf, '\n' );
    if ( !newline )
    {
      fputs( stderr, "Input too long - rejecting\n" );
      while ( fgetc( stream ) != '\n' )
        ; // empty loop
      return 0;
    }

    /**
     * Overwrite the newline with the string terminator.
     */
    *newline = 0;

    /**
     * Use strtol to convert the text to an integer
     * value. chk will point to the first character
     * *not* converted - if this character is anything
     * but whitespace or a 0, the input is not a valid
     * integer.  Store the result to a temporary variable
     * until we're sure it's valid.
     */
    char *chk;
    int tmp = strtol( inbuf, &chk, 10 );
    if ( !isspace( *chk ) && *chk != 0 )
    {
      fprintf( stderr, "%s is not a valid integer!\n", inbuf );
      return 0;
    }
    *var = tmp;
    return 1;  
  }
  fputs( "Error on fgets!\n", stderr );
  return 0;
}
  • Related