Bittu970
Bittu970

Reputation: 35

Is there an better/efficient way to keep asking for user input from STDIN using fgets() in C?

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

#define MAXSIZE 10

int main(int argc, char *argv[]){
    char input[MAXSIZE], c, *input_ptr;
    input_tag: printf("Enter the initial data string : ");
    input_ptr = fgets(input, MAXSIZE, stdin);                    //get input
    if(input_ptr != NULL && strcspn(input,"\n") == MAXSIZE-1){   //check if input fits in buffer
        printf("Buffer overflow. Reduce size of input.\n");
        while((c = getchar()) != '\n')                           //loop through rest of STDIN to discard extra characters until newline
        goto input_tag;                                          //Request for user input again
    }
    input[strcspn(input,"\n")] = '\0';                           //Remove newline character
    printf("Input is %s\n", input);
    return 0;
}

Is it possible to keep requesting the user for new input without using the goto statement? Is there a better way to do what I have done? Because, I keep reading that using goto statement should be avoided if possible.

Upvotes: 0

Views: 144

Answers (3)

chux
chux

Reputation: 154156

A sample alternative.

It is not that great considering the restrictive assert() and trouble when reading null characters.

#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
/*
 * Read a line - primarily via fgets() using prompts.
 *
 * Save up to size-1 characters and form a string.  Do not save a '\n'.
 *
 * Return NULL on nothing more read or input error.
 * Re-read if input is too long.
 * Otherwise return buffer.
 */
char* read_a_line(size_t size, char *buffer, const char *prompt,
    const char *reprompt) {
  // Test parameters.
  // Some reasonable functionality is still possible with parameters that do not meet the assert.
  // Yet for simplicity of this demo code, they are enforced via an assert().
  assert(size > 1 && size <= INT_MAX && buffer);

  size_t length;
  for (;;) {
    if (prompt) {
      fputs(prompt, stdout);
    }
    // Get the line.
    if (fgets(buffer, (int) size, stdin) == NULL) {
      return NULL;
    }

    length = strlen(buffer);
    // Was less then the entire buffer used?
    // Note that reading a null character fools this code.
    // Hence a weakness of using `fgets()`.
    if (length + 1 < size) {
      break;
    }
    // Was the last character read a '\n'?
    if (buffer[length] == '\n') {
      break;
    }

    // If the _next_ character read is the end of the line,
    // then that is OK.
    int ch = fgetc(stdin);
    if (ch == '\n') {
      break;
    }
    if (ch == EOF) {
      return feof(stdin) ? buffer : NULL /* Input error */;
    }

    // Line is too long.  Now read rest of it.
    // Here we could use `fgets()` again,
    // yet the various cases complicate code, so go for simplicity.
    do {
      ch = fgetc(stdin);
      if (ch == EOF) {
        return NULL;
      }
    } while (ch != '\n');

    prompt = reprompt;
  }

  // Lop off a potential '\n'.
  if (length > 0 && buffer[length - 1] == '\n') {
    buffer[--length] = '\0';
  }
  return buffer;
}

Upvotes: 1

xing
xing

Reputation: 2528

A do/while loop can be used to avoid goto.

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

#define MAXSIZE 10

int main ( void){
    char input[MAXSIZE] = "";
    int c = 0;
    size_t count = 0;

    do {
        printf("Enter the initial data string : ");
        if ( NULL == fgets(input, MAXSIZE, stdin)) {
            fprintf ( stderr, "problem fgets returned NULL\n");
            return 1;
        }
        count = strcspn ( input, "\n"); // count to newline or terminating zero
        // printf ( "count %zu\n", count);
        if ( input[count] != '\n') { // did not count to newline
            while ( ( c = getchar ( )) != '\n') {
                if ( EOF == c) {
                    if ( 0 != count) {
                        break;
                    }
                    fprintf ( stderr, "found EOF\n");
                    return 1;
                }
            }
            if ( EOF != c) {
                printf ( "\nBuffer overflow. Reduce size of input.\n");
            }
        }
    } while ( input[count] != '\n' && EOF != c);

    input[count] = '\0'; //Remove newline character
    printf ( "Input is %s\n", input);
    return 0;
}

Upvotes: 2

chux
chux

Reputation: 154156

Is there a better way to do what I have done?

Yes.

Code has at least these problems:

No loop

while((c = getchar()) != '\n') goto input_tag; does not loop.

Maybe OP wanted while((c = getchar()) != '\n'); goto input_tag;.

IAC, a goto is not needed. Another while() loop is a better alternative.

Potential UB

input[strcspn(input,"\n")] = '\0'; is attempted when the prior fgets(input, ...) returns NULL. input[] might not contain string.

Likewise for printf("Input is %s\n", input);.

Infinite loop

char c; ... while((c = getchar()) != '\n') is an infinite loop when getchar() returns EOF due to an end-of-file.

c should be an int.

while((c = getchar()) != '\n' && C != EOF) is more common and not so error prone.

Wrong check in a corner case

strcspn(input,"\n") == MAXSIZE-1 is not certainly wrong when the next character read is an end-of-file indication.


I do not see a great alternative solution using fgets() to read a line.

Things to consider (some are pedantic):

  1. Form a helper function.

  2. How to indicate that the line is too long.

  3. How to cope when null characters are read? How to inform caller of # of characters read?

  4. Proper handling of an input error vs. end-of-file.

  5. Distinguishing reading some text and then encountering end-of-fife vs. a line.

  6. Reading into a buffer longer than INT_MAX - uber pedantic.


IMHO, C lacks a great read_a_line() function.

I'd want something like:

int read_a_line(FILE *stream, size_t dsize, char dest[dsize], size_t *length_read) returning 1 on unqualified success, EOF on nothing read/input error and 0 otherwise.

Good luck.

Upvotes: 5

Related Questions