4

I am trying to write a simple program which will read two input lines, an integer followed by a string. However, it doesn't seem to work for me.

int main()
{
    int i;
    char str[1024];

    scanf("%d", &i);
    scanf("%[^\n]", str);
    printf("%d\n", i);
    printf("%s\n", str);

    return 0;
}

Immediately after entering the integer and pressing "Enter", the program prints the integer. It doesn't wait for me to enter the string. Whats wrong? Whats the correct way to program this?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Bibhu
  • 59
  • 1
  • 4
  • 1
    The input functions in the C standard library are woefully inadequate. Eventually you'll tire of them, and write your own. The problem is - I think - that the second `scanf` reads the `\n` of the first line. You can skip it with something like `scanf(" %`; note the space. – Bathsheba Apr 07 '16 at 07:07
  • 1
    How about a single call to `scanf()` where you get both values? `if (scanf("%d %s", &i, str) == 2) { printf("%d %s\n", i, str); }`. – trojanfoe Apr 07 '16 at 07:09
  • 5
    [How to read / parse input in C - the FAQ](http://stackoverflow.com/questions/35178520/how-to-read-parse-input-in-c-the-faq), with special attention to the chapters "Do not use *scanf() for potentially malformed input", and "When *scanf() does not work as expected". – DevSolar Apr 07 '16 at 07:24
  • `%[^\n]` will read the rest of the same line that `%d` was on. If you want to read the second line you need to move past the first line first. – M.M Apr 07 '16 at 07:56

3 Answers3

6

What you need to know

The problem with %[^\n] is that it fails when the first character to be read is the newline character, and pushes it back into the stdin.

The Problem

After you enter a number for the first scanf, you press Enter. %d in the first scanf consumes the number, leaving the newline character ('\n'), generated by the Enter keypress, in the standard input stream (stdin). %[^\n] in the next scanf sees this \n and fails for the reason given in the first paragraph of this answer.

Fixes

Solutions include:

  • Changing scanf("%d", &i); to scanf("%d%*c", &i);. What %*c does is, it scans and discards a character.

    I wouldn't recommend this way because an evil user could trick the scanf by inputting something like <number><a character>\n, ex: 2j\n and you'll face the same problem again.

  • Adding a space (any whitespace character will do) before %[^\n], i.e, changing scanf("%[^\n]", str); to scanf(" %[^\n]", str); as @Bathsheba mentioned in a comment.

    What the whitespace character does is, it scans and discards any number of whitespace characters, including none, until the first non-whitespace character.

    This means that any leading whitespace characters will be skipped when inputting for the second scanf.

  • This is my recommendation: Clear the stdin after every scanf. Create a function:

    void flushstdin(void)
    {
        int c;
        while((c = getchar()) != '\n' && c != EOF);
    }
    

    and call it after every scanf using flushstdin();.

Other issues:

Issues unrelated to your problem include:

  • You don't deal with the case if scanf fails. This can be due to a variety of reasons, say, malformed input, like inputting an alphabet for %d.

    To do this, check the return value of scanf. It returns the number of items successfully scanned and assigned or -1 if EOF was encountered.

  • You don't check for buffer overflows. You need to prevent scanning in more than 1023 characters (+1 for the NUL-terminator) into str.

    This can be acheived by using a length specifier in scanf.

  • The standards require main to be declared using either int main(void) or int main(int argc, char* argv[]), not int main().
  • You forgot to include stdio.h (for printf and scanf)

Fixed, Complete Program

#include <stdio.h>

void flushstdin(void)
{
    int c;
    while((c = getchar()) != '\n' && c != EOF);
}

int main(void)
{
    int i;
    char str[1024];

    int retVal;

    while((retVal = scanf("%d", &i)) != 1)
    {
        if(retVal == 0)
        {
            fputs("Invalid input; Try again", stderr);
            flushstdin();
        }
        else
        {
            fputs("EOF detected; Bailing out!", stderr);
            return -1;
        }
    }

    flushstdin();

    while((retVal = scanf("%1023[^\n]", str)) != 1)
    {
        if(retVal == 0)
        {
            fputs("Empty input; Try again", stderr);
            flushstdin();
        }
        else
        {
            fputs("EOF detected; Bailing out!", stderr);
            return -1;
        }
    }

    flushstdin();

    printf("%d\n", i);
    printf("%s\n", str);

    return 0;
}
Community
  • 1
  • 1
Spikatrix
  • 20,225
  • 7
  • 37
  • 83
1

This simply, will work:

scanf("%d %[^\n]s", &i, str);
Lincoln Cheng
  • 2,263
  • 1
  • 11
  • 17
0

Instaed of scanf() use fgets() followed by sscanf().

Check return values of almost all functions with a prototype in <stdio.h>.

#include <stdio.h>

int main(void) {
    int i;
    char test[1024]; // I try to avoid identifiers starting with "str"

    char tmp[10000]; // input buffer

    // first line
    if (fgets(tmp, sizeof tmp, stdin)) {
        if (sscanf(tmp, "%d", &i) != 1) {
            /* conversion error */;
        }
    } else {
        /* input error */;
    }
    // second line: read directly into test
    if (fgets(test, sizeof test, stdin)) {
        size_t len = strlen(test);
        if (test[len - 1] == '\n') test[--len] = 0; // remove trailing ENTER

        // use i and test
        printf("i is %d\n", i);
        printf("test is \"%s\" (len: %d)\n", test, (int)len);

    } else {
        /* input error */;
    }
    return 0;
}
pmg
  • 106,608
  • 13
  • 126
  • 198