The point here is not one of inconsistency, but one of the many limitations of the fscanf()
family.
The standard is very specific on how fscanf()
parses input. Characters are taken from input one by one, and checked against the format string. If they match, the next character is taken from input. If they don't match, the character is "put back", and the conversion fails.
But only that last character read is ever put back.
C11 7.21.6.2 The fscanf function, paragraph 9 (emphasis mine):
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.
- 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.
This one character of push-back has nothing to do with the one character of push-back that ungetc()
guarantees -- it is independent and in addition to that. (A user could have fscanf()
fail, then ungetc()
a character, and expect the ungetc()
'd character to come out of input, followed by the character pushed back by the failed fscanf()
. *A library function may not call ungetc()
, which is reserved to the user.)
This makes implementing the scanning fscanf()
somewhat easier, but also makes fscanf()
fail in the middle of certain character sequences, without actually retracing to where it began its conversion attempt.
In your case, "--123"
read as "%d"
:
- taking the first
'-'
. Sign. All is well, continue.
- taking the second
'-'
. Matching error.
- Put back the last
'-'
. Cannot put back the second '-'
as per above.
- Return
0
(conversion failed).
This is (one of) the reason(s) why you should not ever use *scanf()
on potentially malformed input: The scan can fail without you knowing where exactly it failed, and without properly rolling back.
It's also a murky corner of the standard that was not actually implemented correctly in a number of mainstream library implementations last time I checked. (And not when I re-checked just now.) ;-)
Other reasons not to use fscanf()
on potentially malformed input include, but are not limited to, numerical overflows handled not at all gracefully.
The intended use of fscanf()
is to scan known well-formatted data, ideally data that has been written by that same program using fprintf()
. It is not well-suited to parse user input.
Hence the usual recommendation is to read full lines of input with fgets()
, then parse the line in-memory using strtol()
, strtod()
etc., which can and will handle things like the above in a well-defined way.