1

(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
}
Steve Summit
  • 45,437
  • 7
  • 70
  • 103
XanJo
  • 41
  • 6
  • Check the return value from `scanf("%d", &i);`. If it is not `1` then it failed. – 001 Oct 05 '22 at 17:40
  • @JohnnyMopp Unfortunately that doesn't quite meet the requirements. `scanf` will happily return 1 for `%d` if the user enters `12.34`. – Steve Summit Oct 05 '22 at 17:41
  • It's not a complete solution but it meets part of it. For example, if the user enters "Hello". – 001 Oct 05 '22 at 17:41
  • 4
    @XanJo This is a surprisingly difficult problem. It's been asked many times before. It's almost impossible to solve completely if you start with `scanf`. – Steve Summit Oct 05 '22 at 17:42
  • 4
    Don't use `scanf`. Use `fgets` and `strtol` See my answer: [Check if all values entered into char array are numerical](https://stackoverflow.com/a/65013419/5382650) Here's a crude version: `char buf[100]; fgets(buf,sizeof(buf),stdin); buf[strcspn(buf,"\n")] = 0; char *cp; i = strtol(buf,&cp,10); if (*cp != 0) printf("error\n");` – Craig Estey Oct 05 '22 at 17:43
  • 1
    @CraigEstey `getline()`, if available, is a much better option than `fgets()`. – Andrew Henle Oct 05 '22 at 17:50
  • @AndrewHenle For getting whole lines, I'd agree. But, to prompt for a number, IMO, it's overkill because it requires a `malloc/free`. And, as you mentioned, "if available". `fgets` is ubiquitous. – Craig Estey Oct 05 '22 at 17:56
  • Craig, I'm not familiar with fgets and strtol, but I'm going to give them a shot. Thank you for your input. – XanJo Oct 05 '22 at 18:14
  • 1
    @XanJo: I believe that my function `get_int_from_user` from [this answer of mine](https://stackoverflow.com/a/73915292/12149471) does everything you need. It will reject input such as `1 12 7` and automatically reprompt the user for input. This function uses `fgets` and `strtol`. – Andreas Wenzel Oct 05 '22 at 20:37

2 Answers2

1

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

"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.

In particular, it's mildly tricky to differentiate between trailing non-numeric garbage, which you might want to disallow, and trailing whitespace (including \n), which you might want to allow. And it can be downright hard to differentiate between numbers that do or don't fit into a 32-bit int, or whatever size object you're reading into.

(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.]

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Steve, thank you for your post. I'm only interested in decimal base-10 input, and hoping to exit the program if anything else is given. It does seem I won't be able to do what I need to using scanf. I'm not familiar with fgets or strtol, but I'm hoping I'll be able to use them for my solution. – XanJo Oct 05 '22 at 18:14
1

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;
}
John Bode
  • 119,563
  • 19
  • 122
  • 198