11

Here, I saw this statement in the accepted answer:

Most of the conversion specifiers skip leading whitespace including newlines but %c does not.

For me it is not clear the rationale under this different behaviors, I would have expected a uniform one (e.g. always skipping or never).

I came into this kind of problem with a piece of C code like this:

#include "stdio.h"

int main(void){

    char ch;
    int actualNum;

    printf("Insert a number: ");
    scanf("%d", &actualNum);
    // getchar();

    printf("Insert a character: ");
    scanf("%c", &ch);

    return 0;
}

Swapping the two scanfs solves the problem, as well as the (commented) getchar, otherwise the '\n' of the first insertion would be consumed by the second scanf with %c. I tested on gcc both on linux and windows, the behavior is the same:

gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2)
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

So my question is: Why does %d and %c behave differently w.r.t. '\n' in scanf?

Community
  • 1
  • 1
Alessandro S.
  • 875
  • 12
  • 24
  • 3
    The `"%c"` format is used to read a single character, even if it is a space. How else would you do that? – Bo Persson Nov 07 '12 at 18:00
  • You could use `scanf(" %c", &ch);` (with a space before the %). – Cubbi Nov 07 '12 at 18:00
  • See http://stackoverflow.com/questions/12926268/when-does-scanf-start-and-stop-scanning – TheBlastOne Nov 07 '12 at 18:01
  • Not sure what your question actually is. Please edit your posting to provide a specific question. – Richard Chambers Nov 07 '12 at 18:03
  • 1
    Note that behavior is dependent on the buffering mode applied to `stdin`. On a terminal, it defaults to line-buffered. That means in effect that input is provided in bursts, as signaled by newline. Neither call to `scanf` will even see any characters until the line is terminated, **but the newline is included in the input**. If `stdin` is redirected from a file, the buffering is different and the behavior will appear to change. – RBerteig Nov 07 '12 at 19:09
  • @RichardChambers: thank you, the question was not explicitly stated, I edited it hoping it is more clear. – Alessandro S. Nov 08 '12 at 08:48

4 Answers4

13

It is consistent behavior, you're just thinking about it wrong. ;)

scanf("%c", some_char); // reads a character from the key board.
scanf("%d", some_int);  // reads an integer from the key board.

So if I do this:

printf("Insert a character: ");
scanf("%c", &ch);                // if I enter 'f'. Really I entered 'f' + '\n'
                                 // scanf read the first char and left the '\n'
printf("Insert a number: ");
scanf("%d", &actualNum);      // Now scan if is looking for an int, it sees the '\n'
                              // still, but that's not an int so it waits for the 
                              // the next input from stdin

It's not that it's consuming the newline on its own in this case. Try this instead:

char ch;
char ch2;
printf("Insert a character: ");
scanf("%c", &ch);
printf("Insert another character: ");
scanf("%c", &ch2); 

It will "skip" the second scanf() because it reads in the '\n' at that time. scanf() is consistent and you MUST consume that '\n' if you're going to use it correctly.

Mike
  • 47,263
  • 29
  • 113
  • 177
  • thank you very much, even if your answers were equivalently clear the "horse's mouth" of the accepted one was an extra value for me! – Alessandro S. Nov 08 '12 at 08:51
  • This logic doesn't really work; if `scanf("%d"` sees a letter then it doesn't skip it and wait for an int still. – M.M Nov 12 '14 at 03:34
  • @Matt Yes, you are correct. I was trying to simplify the answer for the OP and I think I over simplified it, I changed it slightly to remove the error.. but I still need come up with better wording – Mike Nov 12 '14 at 17:22
11

From the horse's mouth:

7.21.6.2 The fscanf function

...
5 A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read. The directive never fails.
...
7 A directive that is a conversion specification defines a set of matching input sequences, as described below for each specifier. A conversion specification is executed in the following steps:

8 Input white-space characters (as specified by the isspace function) are skipped, unless the specification includes a [, c, or n specifier. 284)

9 An input item is read from the stream, unless the specification includes an n specifier. An input item is defined as the longest sequence of input characters which does not exceed any specified field width and which is, or is a prefix of, a matching input sequence. 285) The first character, if any, after the input item remains unread. If the length of the input item is zero, the execution of the directive fails; this condition is a matching failure unless end-of-file, an encoding error, or a read error prevented input from the stream, in which case it is an input failure.
284) These white-space characters are not counted against a specified field width.
285) fscanf pushes back at most one input character onto the input stream. Therefore, some sequences that are acceptable to strtod, strtol, etc., are unacceptable to fscanf.

Emphasis added by me.

Whitespace is not part of a valid integer string, so it makes sense for the %d conversion specifier to skip any leading whitespace. However, whitespace may be valid on its own, so it makes sense for the %c conversion specifier to not skip it.

Per clause 5 above, if you put a blank space in the format string prior to the %c directive, all leading whitespace will be skipped:

scanf(" %c", &ch);
Spikatrix
  • 20,225
  • 7
  • 37
  • 83
John Bode
  • 119,563
  • 19
  • 122
  • 198
2

It is because whitespace can never be an integer, but whitespace is made up of characters. If you specifically want a character try something like

scanf("%c", &ch );
while(isspace(c))
    scanf("%c" , &ch);

Or use !isalnum() if you want to allow only letters and numbers or !isalpha() for only letters.

asbumste
  • 451
  • 3
  • 12
-2

I'm no expert, but i've found that doing:

fflush(stdin);

after every scanf helps..

Such as:

scanf("%c", &ch);
fflush(stdin);
Draconian Times
  • 319
  • 1
  • 3
  • 12
  • 5
    The behavior of `fflush` is not defined for input streams; what it does for any particular system is not guaranteed to be correct or repeatable. Do not use `fflush` to clear the input stream. – John Bode Nov 07 '12 at 18:45
  • 1
    Ok... I get it. fflush used in this manner has undefined behavior. – Draconian Times Nov 09 '12 at 10:44
  • @JohnBode What do you suggest can be used to clear any pending bytes in the input stream? – Sreenikethan I Dec 24 '22 at 08:20
  • @SreenikethanI: For text input, call `getchar()` or `fgetc()` in a loop until you see a newline or `EOF`: `int c; while ((c = getchar()) != '\n' && c != EOF );`. – John Bode Dec 29 '22 at 16:36