1

I have declared a buffer of size 10 that accepts AT most 9 characters, if I write any character over 10, I wish for my code to print the word invalid, flush stdin (clear fgets) and accepts new input once again like normal.

This is the code I currently have

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

int main() {
    char buffer[10];
    while (fgets(buffer, 10, stdin) != NULL) {
        buffer[strcspn(buffer, "\n")] = 0; // remove newline character
        if (strlen(buffer) > 9) {
            printf("Input too large\n");
            fflush(stdin); // clear input buffer before reading new input
            continue;
        }
        // process input here
        printf("Input: %s\n", buffer);
        fflush(stdin); // clear input buffer before reading new input
    }
    return 0;
}

When I type "happy"

It prints

Input: Happy

And accepts new user input

When I type "Happy11111"

It prints

Input: Happy1111
Input: 1

As you can see I want it to print input is invalid and then clear fgets to accept and re-read new input

How would I go about doing this?

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • Related: https://stackoverflow.com/questions/2979209/using-fflushstdin – user3386109 Mar 11 '23 at 05:18
  • Where should I place this? – HyperCoderSuperion Mar 11 '23 at 05:19
  • 1
    The point of that link is that you should never use `fflush(stdin)`. Instead, you need to write a loop that reads characters until it reads the newline character. – user3386109 Mar 11 '23 at 05:20
  • 1
    Also, `strlen(buffer)` can never be greater than 9, because the tenth character is used for the NUL terminator. – user3386109 Mar 11 '23 at 05:22
  • When do you want your program to terminate? Does the user have to enter end-of-file on the console/terminal by pressing a special key combination that depends on the operating system? That seems to be how you programmed it, but I am not sure if this was intentional. – Andreas Wenzel Mar 11 '23 at 05:29
  • the user can terminate by typing in end This terminates the program by calling exit – HyperCoderSuperion Mar 11 '23 at 05:34
  • No need to call `strlen()`, you have already don't that in `strcspn()`. Simple declare `size_t len;` and then `buffer[(len = strcspn(buffer, "\n"))] = 0;` to overwrite the `'\n'` with `0` and save `len` at the same time. Do not `fflush(stdin);` - that is pure ***undefined behavior*** according to the C standard (despite what MS provides as an extension). Simply `for (int c = getchar(); c != EOF; c = getchar()) {}` to read and discard any characters that remain in your input buffer (`stdin`). – David C. Rankin Mar 11 '23 at 07:04
  • @DavidC.Rankin: your suggestion regarding `strlen()` is correct, but purpose of determining the length of the string is vain in this particular case. – chqrlie Mar 11 '23 at 07:09
  • @chqrlie - yep, read your answer. Only other alternative is to check for the `'\n'` before overwriting and if the `'\n'` was not read a `getchar()` and `ungetc()` can guarantee `getchar()` won't block in the read/discard remaining character loop. – David C. Rankin Mar 11 '23 at 07:13

2 Answers2

3

There is no way to tell fgets() to report an error if the line is longer than the available buffer length. To achieve your goal, you must write your own line read function. You can also make it strip the newline:

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

// read a full line from fp
// return the total number of characters read excluding the newline
// return -1 at end of file
int mygets(char *p, int size, FILE *fp) {
    int c;
    int i = 0;
    while ((c = getc(fp)) != EOF && c != '\n') {
        if (i + 1 < size) {
            p[i] = (char)c;
        }
        i++;
    }
    if (c == EOF && i == 0) {
        return -1;
    }
    if (i + 1 < size) {
        p[i] = '\0';
    } else
    if (size > 0) {
        p[size - 1] = '\0';
    }
    return i;
}

int main(void) {
    char buffer[10];
    int len;
    while ((len = mygets(buffer, sizeof buffer, stdin)) >= 0) {
        if (len > (int)sizeof(buffer) - 1) {
            printf("Input too large\n");
            continue;
        }
        if (!strcmp(buffer, "end"))
            break;
        // process input here
        printf("Input: %s\n", buffer);
    }
    return 0;
}

Also note that fflush(stdin) has undefined behavior. It may discard the pending line on some systems, or do nothing or have any other side effect...

Here is a function to read and discard the rest of the input line (eg: after a call to scanf()) using a simple loop:

// read and discard the rest if the input line
// return EOF at end of file, otherwise return '\n'
int flush_line(FILE *fp) {
    int c;
    while ((c = getc(fp)) != EOF && c != '\n')
        continue;
    return c;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

In C, the statement fflush(stdin) will invoke undefined behavior, which means that anything can happen. It generally does not guarantee any specific behavior. Even if this works as intended on one platform, you cannot be sure that it will also work on another platform. See this question for further information.

For this reason, you should not use fflush(stdin), but you should rather examine whether the result of fgets contains a newline character. That way, you can determine whether the entire line was read in or not. If not, then you can read and discard the remainder of the line, for example by calling getchar() in a loop. In contrast to fflush(stdin), this is guaranteed to work on all platforms.

The line

if (strlen(buffer) > 9) {

is not meaningful, because buffer is unable to store a string whose length is larger than 9. Therefore, this condition will always be false. Instead, you should check whether buffer contains a newline character or not, as already mentioned above.

Also, your posted code is designed in such a way that the program will terminate when the user enters end-of-file in the console/terminal. However, this requires the user to press a special key combination which depends on which operating system is being used. In the comments section, you stated that you instead want the user to enter "end" when the program should terminate.

Here is an example program which implements everything that I mentioned above:

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

void get_line_from_user( const char prompt[], char buffer[], int buffer_size );

int main( void )
{
    char buffer[10];

    for (;;) //infinite loop, equivalent to while(1)
    {
        get_line_from_user(
            "Please enter up to 9 characters: ",
            buffer, sizeof buffer
        );

        if ( strcmp( buffer, "end" ) == 0 )
        {
            printf( "Ending program.\n" );
            exit( EXIT_SUCCESS );
        }

        printf( "Input: %s\n", buffer );
    }
}

//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. If
//the line is too long to fit in the buffer, then the function
//will automatically reprompt the user for input. On failure,
//the function will never return, but will print an error
//message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
    for (;;) //infinite loop, equivalent to while(1)
    {
        char *p;

        //prompt user for input
        fputs( prompt, stdout );

        //attempt to read one line of input
        if ( fgets( buffer, buffer_size, stdin ) == NULL )
        {
            printf( "Error reading from input!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to find newline character
        p = strchr( buffer, '\n' );

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small to store the entire line)
        if ( p == NULL )
        {
            int c;

            //a missing newline character is ok if the next
            //character is a newline character or if we have
            //reached end-of-file (for example if the input is
            //being piped from a file or if the user enters
            //end-of-file in the terminal itself)
            if ( (c=getchar()) != '\n' && !feof(stdin) )
            {
                printf( "Input was too long to fit in buffer!\n" );

                //discard remainder of line
                do
                {
                    if ( c == EOF )
                    {
                        printf( "Error reading from input!\n" );
                        exit( EXIT_FAILURE );
                    }

                    c = getchar();

                } while ( c != '\n' );

                //reprompt user for input by restarting loop
                continue;
            }
        }
        else
        {
            //remove newline character by overwriting it with
            //null character
            *p = '\0';
        }

        //input was ok, so break out of loop
        break;
    }
}

This program has the following behavior:

Please enter up to 9 characters: Happy
Input: Happy
Please enter up to 9 characters: Happy11111
Input was too long to fit in buffer!
Please enter up to 9 characters: Happy1111
Input: Happy1111
Please enter up to 9 characters: end
Ending program.
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • I'm afraid your function incorrectly complains about `input too long` and exits if the input stream contains exactly `buffer_size-1` bytes without a newline. In this case, `feof()` returns `0` and `getc(fp)` returns `EOF` and set the end-of-file indicator. One more example of why `feof()` is not your friend :) – chqrlie Mar 11 '23 at 06:18
  • 1
    @chqrlie: Well spotted! I have now swapped the order of the operands of the `&&` operation, but I am still not quite happy with this. I believe it handles end-of-file properly now, but if a stream error occurs for the first time in my call to `getchar`, then my program may report that the input was too long. Maybe I should also check for `ferror`. – Andreas Wenzel Mar 11 '23 at 07:03
  • Yes, I was wondering about that too, and there is another potential problem: if the file contains embedded null bytes, `strchr()` may return `NULL` even if the newline was read within the buffer length. This is a pathological corner case, but more likely than a read error on modern systems. – chqrlie Mar 11 '23 at 07:07