0

i just finished a project for school and im now polishing some simple things i ignored until the program worked fine. Im almost done but I realized I dont know how to limit the amount of chars i can accept on input. For ex: i have this fragment of code:

printf("\nIngrese su intento:  ");
scanf("%s", &intento);

The project is a wordle clone so it ALWAYS should be 5 chars, not 1more or 1less, how do i limit it to only accept 5 and in case user inputs lets say 8, tell the user "no, do it again". Googling i found this:

Anything or nothing. Welcome to undefined behavior. Writing beyond the end of an array / buffer causes undefined behavior. What happens when undefined behavior happens is not predictable. Maybe it works correctly. Maybe your program crashes. Maybe you corrupt data and cause a security problem. Maybe your hard-drive gets formatted.

Ok. Now i know that even though it sometimes work, i shouldnt do it cause in some random case, it might not... how do i fix this? how do i limit the input with chars? i've already done this with int because its easier but i dont know how to approach it with text.

printf("Bienvenido a Wordle, cuantas partidas deseas jugar en tu sesion de juego?  ");
scanf("%d", &cantPartidas);

while (cantPartidas > 8 || cantPartidas < 1) {

    printf("\nLo sentimos, el numero de partidas por sesion tiene que estar entre 1 y 8 :( \nIngresa un numero dentro del rango:  ");
    scanf("%d", &cantPartidas);
}

printf("\nGenial! Empecemos.");
  • 1
    [Read no more than size of string with scanf()](https://stackoverflow.com/questions/12306591/read-no-more-than-size-of-string-with-scanf) – yano Jul 14 '22 at 19:55
  • 1
    1) I was going to reply `scanf("%5", &intento);`, but Mateo Vozila beat me to it. Please "upvote" and/or "accept" his answer. 2) I would discourage "[scanf()](https://en.cppreference.com/w/c/io/fscanf)", and consider using "[fgets()](https://en.cppreference.com/w/c/io/fgets)" instead. 3) If you use scanf(), then *ALWAYS* check the result (to check for input errors!) – paulsm4 Jul 14 '22 at 20:14
  • Your sample code to limit the input for integers does not work. If the input stream is a string that cannot be represented in an integer (eg, converting the string would result in a value larger than INT_MAX), the behavior is undefined. If you want to read an integer between 1 and 8, then you can tell scanf to read only one character. eg `scanf("%1d")` – William Pursell Jul 14 '22 at 20:15
  • Thank @WilliamPursell !!! i didnt think about that one, so basically i should always determine the length of the input with "%1d" (or whatever other number) , right? – Valentín Sanabria Jul 14 '22 at 20:29
  • @paulsm4 hey, just marked it as correct answer! i was going to ask you, is there a simple way of explaining why using fgets is better/safer than scanf? im planning on searching a complete explanation later cause im guessing its a complex thing but right now i have so much to google that i would appreciate a brief explanation so i can keep coding the school project. – Valentín Sanabria Jul 14 '22 at 20:33
  • 1
    Typically, people don't bother putting a maximum field width on `%d`, but doing so is the only way to avoid UB (unless you control the input). The best thing to do is stop using scanf. – William Pursell Jul 14 '22 at 20:37
  • Side note: Your posted code would be easier to understand if you translated the output to English. – Andreas Wenzel Jul 14 '22 at 21:18
  • @Valentín Sanabria: using "%ld" is silly. As William Pursell added to his own suggestion, you're *REALLY* interested in "validating" whether the integer value is 1..8, not whether or not it's one digit. "scanf()" doesn't really offer much in the way of "validation". That's why "fgets()" is often a better choice, as [Andrea Wenzel explains below](https://stackoverflow.com/a/72987063/421195). In any case - *ALWAYS CHECK THE RETURN FROM "SCANF()"!*. – paulsm4 Jul 14 '22 at 22:19

2 Answers2

2

You can limit the upper bound by using %5s like so

scanf("%5s", cantPartidas);

This makes sure you don't write into memory that is not allocated. Generally, the length of any string can be checked with the strlen() function that is in the string.h library so you can use that to make sure your string has exactly 5 chars in it.

  • OP stated that they want the program to reject the input and to print an error message, if the input is longer than 5 characters. Therefore, OP needs a way to detect how long the input was. Your solution does not provide this possibility. Instead, your solution silently truncates the input to 5 characters, and you do not specify how the program can detect whether such a truncation occurred. – Andreas Wenzel Jul 14 '22 at 21:21
  • This is an excellent answer to the "scanf()" question the OP asked. It's also worth noting that one of the limitations of scanf() with "%5s" is that you'll *ALWAYS* get at most 5 characters ... regardless if the user actually entered 20. – paulsm4 Jul 14 '22 at 22:23
  • I actually tried adding this in the code for a while now and it always gives me problems. i wrote " scanf("%5s", &intento); " and then proceeded to run the code and wrote a very long string, and it just does weird stuff, mostly it counts those exceeded chars into the next scanf and takes it as if I had made two different inputs instead of one. – Valentín Sanabria Jul 14 '22 at 22:34
  • @ValentínSanabria: Yes, that is precisely what I meant when I wrote "The function `scanf` will do strange unintuitive things, such as not always read an entire line of input at once." in my answer. It is possible to solve this problem though, by discarding the remainder of the line. Search for the comment "discard remainder of line" in the code of my answer on how to do that. This will also work with `scanf`. However, as stated in my answer, I do not recommend that you use `scanf`. – Andreas Wenzel Jul 14 '22 at 22:48
1

In order to read an entire line of input, I recommend that you use the function fgets. I do not recommend that you use the function scanf, because it is not designed for line-based user input.

The function scanf will do strange unintuitive things, such as not always read an entire line of input at once. The function fgets, on the other hand, will always read exactly one line of user input, assuming that the supplied memory buffer is large enough to store the entire line.

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

int main( void )
{
    char line[200];

    //repeat forever until input is valid
    for (;;) //infinite loop, equivalent to while(1)
    {
        char *p;

        //prompt user for input
        printf( "Enter your attempt: " );

        //attempt to read one line of input
        if ( fgets( line, sizeof line, stdin ) == NULL )
        {
            fprintf( stderr, "Input error!\n" );
            exit( EXIT_FAILURE );
        }

        //find newline character, if it exists
        p = strchr( line, '\n' );

        //determine whether newline character was missing
        if ( p == NULL )
        {
            int c;

            printf( "Line too long for input buffer!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

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

            continue;
        }

        //remove newline character
        *p = '\0';

        //make sure that exactly 5 characters were entered
        if ( p - line != 5 )
        {
            printf( "Please enter exactly 5 characters!\n" );
            continue;
        }

        //input is valid, so break out of infinite loop
        break;
    }

    printf( "Input is valid.\n" );
}

This program has the following behavior:

Enter your attempt: mike
Please enter exactly 5 characters!
Enter your attempt: jeremy
Please enter exactly 5 characters!
Enter your attempt: james
Input is valid.
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • I like this response, its really useful for me, even more since using the %5s is giving me errors in the code. But i have 2 questions to mark it as correct answer. First what does "p - line" mean? i dont understand what are u subctracting. And secondly, what does "continue" (on that same if condition) do? Shouldn't you just do a while loop that has "p - line != 5" as a condition and be stuck there until you input something that has 5chars? – Valentín Sanabria Jul 14 '22 at 22:30
  • @ValentínSanabria: `p - line` is equivalent to `strlen( line )`, but more efficient. Since `p` is pointing to the terminating null character at the end of the string (where the newline character used to be) and the expression `line` [decays](https://stackoverflow.com/q/1461432/12149471) to a pointer to the start of the string, the result of the subtraction is the difference between the two pointers, which is the length of the string. – Andreas Wenzel Jul 14 '22 at 22:35
  • @ValentínSanabria: Yes, instead of an infinite `for (;;)` or `while(1)` loop, you could also use `while( p - line != 5 )` (assuming that `p` and `line` were still in [scope](https://en.cppreference.com/w/c/language/scope)). However, if you did that, then where would you want to put the statement `printf( "Please enter exactly 5 characters!\n" );`? I don't see how you could solve that except by performing the check `p - line != 5` multiple times, so using an infinite loop seems better in this case. – Andreas Wenzel Jul 14 '22 at 22:41
  • @ValentínSanabria: The [`continue`](https://en.cppreference.com/w/c/language/continue) statement will end the current loop iteration and go to the next loop iteration. – Andreas Wenzel Jul 14 '22 at 23:24
  • @ValentínSanabria: The reason why `strlen` is less efficient than using pointer arithmetic is that `strlen` must traverse the entire string, in order to find the length of the string. Therefore, if you have a pointer to the start of the string and a pointer to the end of the string, it is generally more efficient to calculate the length of the string using pointer arithmetic, than using `strlen`. However, in this case, when the string is probably only a few characters long, it probably does not matter which option you use. – Andreas Wenzel Jul 15 '22 at 18:08