0

I have an unusual problem regarding the code below.

void Menu()
{
    bool end = 0;
    int n;
    while (!end)
    {
        scanf("%d", &n);
        switch(n)
        {
            case 1:
                my_strlen_show();
                break;
            //other irrelevant cases 2-6
            case 7:
                end = 1;
                break;
            default:
                printf("ERROR");
                break;
        }
    }       
}

int my_strlen(const char *str)
{
    int count = 0;
    for (; *str != '\0' ; str++)
    {
        count++;
    }
    return count;
}

void my_strlen_show()
{
    
    char tab[1000];
    printf("\n\nEnter a sentence: ");
    gets(tab);
    gets(tab);
    printf("\nWritten sentence has %d characters.\n\n", my_strlen(tab));
    return;
}

I have no idea why I have to write gets(tab) twice to get the program to work properly. When I use it once, my_strlren_show() function executes instantly and shows that the sentence has 0 characters. I am aware that I can use other methods such as a scanf() function inside a for loop, but I am curious why this method works in a peculiar way.

Can anyone explain why that is the case? I would be very thankful.

Makonde
  • 3
  • 2
  • 5
    DO NOT USE `gets()` , it's dangerous and not part of C standard anymore. Use `fgets()` instead. – Sourav Ghosh Dec 29 '20 at 14:29
  • 1
    Have you checked what `scanf()` actually reads? Or even ***if*** it actually reads any input? – Andrew Henle Dec 29 '20 at 14:29
  • 3
    First of all, [you shouldn't be using `gets` at all](https://stackoverflow.com/q/1694036/11336762). Then: what input do you provide? Do you start with a newline? Probably the first gets just reads the newline character left by `scanf` – Roberto Caboni Dec 29 '20 at 14:31
  • C has no function called gets – Fredrik Dec 29 '20 at 15:26

2 Answers2

2

Do not use gets(). Its dangerous unsafety has earned it the dubious distinction of belonging to a very small set of functions that have been withdrawn from the C language standard.

However, you would probably experience the same issue if you changed to fgets:

    fgets(tab, sizeof(tab), stdin);

The issue is that gets() and fgets() read through the end of the current line (or until the buffer is filled in the case of fgets()). The preceding scanf() consumed only the bytes through the end of a decimal integer, leaving the rest of that line on the input stream, waiting to be read. That includes at least a newline marking the end of the line. That has to be consumed before the wanted input can be read with fgets() or gets(). One way to accomplish that would be:

if ((scanf("%*[^\n]") == EOF) || (getchar() == EOF)) {
    // handle end-of-file or I/O error ...
}

The scanf reads and discards any characters preceding the next newline, and, supposing that the end of the file is not reached and no I/O error occurs, the getchar() consumes the newline itself.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

Your first scanf only reads a single integer from stdin. When you press enter after inputting the integer, a newline (\n) is sent to the stdin, which just stays there - waiting to be picked up by the next function reading from stdin.

The next gets then reads this newline and instantly returns. So you needed another gets to actually read the input.

With that said, you should never even use gets in the first place - it's a deprecated function. On top of that, consider using fgets for reading input. scanf is really an input parsing function, not a reading function. It only reads what it can parse, and leaves everything else in the stdin.

If you still decide to go the scanf route, you should consume the newline on the first input using "%d\n" - not only that, you must also check the return value for scanf, it returns the number of values it was able to parse.

Now the next fgets call won't have to consume the left over newline. It'll wait for another line of user input (note that the newline will be included in the buffer fgets reads into)-

char tab[1000];
printf("\n\nEnter a sentence: ");
if (fgets(tab, 1000, stdin) == NULL)
{
    // handle error during reading
    return;
}
// tab now has the user input, delimited with a `\n`
// if you want to get rid of this newline - use `strcspn`
tab[strcspn(name, "\n")] = 0

Docs on strcspn

I would however, recommend you to go the full fgets route and do the integer parsing with sscanf.

int n;
char buff[4096];
if (fgets(buff, 4096, stdin) == NULL)
{
    // handle error during reading
    return;
}
if (sscanf(tab, "%d", &n) != 1)
{
    // parsing failed - sscanf should've parsed exactly 1 value
    // handle error
    return;
}
// use n here

Here's a full guide on how to move away from scanf - which will mention this specific problem.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Chase
  • 5,315
  • 2
  • 15
  • 41
  • 1
    Note [the effect of trailing white space in a `scanf()` format string](https://stackoverflow.com/questions/19499060/what-is-the-effect-of-trailing-white-space-in-a-scanf-format-string) — and do not ever recommend including trailing white space in a format string. It doesn't do what you expect and forces the user to know what must be typed next. A more sensible alternative is to read lines (with standard C `fgets()` or POSIX `getline()`) and then parse with `sscanf()` or other functions, which you finish up recommending. – Jonathan Leffler Dec 29 '20 at 14:50
  • @JonathanLeffler I completely agree. Though I believe, leaving a trailing `\n` on a `scanf` format string when the input stream is line buffered shouldn't be as harmful. But of course, `stdin` doesn't necessarily have to be line terminated. – Chase Dec 29 '20 at 14:58
  • If the input comes from a terminal, the trailing newline is a UX disaster. It doesn't complete until the user types something that is not white space — something other than a blank, tab or newline. If the input is inevitably a file, rather than a terminal, then you can maybe get away with, but it is still probably not a good idea. If need be, the next input format can start with white space, but only 3 conversion specifiers do not skip white space automatically. They are `%c`, `%[…]` (scan sets), and `%n`. – Jonathan Leffler Dec 29 '20 at 15:14