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 theisspace
function) are skipped, unless the specification includes a[
,c
, orn
specifier. 284)
9 An input item is read from the stream, unless the specification includes ann
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);