Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

More fun with scanf

1 view
Skip to first unread message

graywane

unread,
Jan 13, 2001, 2:23:22 AM1/13/01
to
In article <3a5fd4a8....@198.60.22.3>, Scott Brown wrote:
> I've been muddling around for a solution to the problem described in
> the FAQ section 12.18. I came up with the following:
>
> scanf("%d%*c", &n, NULL);

If you read down to 12.20 you will see that using scanf is rarely a good
idea. It has contributed to several buffer overflow security problems and
even if used properly it is still a pain in the butt. Read strings and parse
them yourself or use std::string and a C++ compiler.

Chris Torek

unread,
Jan 13, 2001, 3:50:14 AM1/13/01
to
In article <3a5fd4a8....@198.60.22.3>

Scott Brown <s...@xmission.com> writes:
>I've been muddling around for a solution to the problem described in
>the FAQ section 12.18. I came up with the following:
>
> scanf("%d%*c", &n, NULL);
>
>It seems to work on my peecee, so I'm just wondering if this is really
>a good (e.g. portable) way to deal with that pesky trailing newline.

The scanf family work in terms of interpreting a sequence of directives.
To find out what any particular format does, work through each
directive in turn, and ask yourself what that directive does in all
possible cases.

The first directive here is "%d". This means:

a) Skip as much white space as possible in the (input) stream, including
blanks, tabs, and entirely blank lines.
b) Now the stream is at EOF -- that is, an fgetc() would return EOF
-- or the next input character is nonblank. If it is nonblank,
it may be part of a valid integer sequence -- i.e., an optional
sign (+ or -) followed by a digit sequence -- or it may not.
b1) EOF: the directive fails with an input failure.
b2) Valid integer sequence of sign and digits: the directive
reads and converts the sign and digits, more or less as if
via atoi() or strtol() with a base of 10.
b3) Valid integer sequence of digits only: the directive
reads and converts the digits as in step (b2).
b4) Valid sign but no valid digits: the directive fails with
a matching failure. The sign is or is not consumed (the
text of the C89 standard implies that it is not, but a
defect report reply says that it is).
b5) No valid sign and no valid digits: the directive fails
with a matching failure. No further input is consumed.

(There is a caveat in (b3) and (b4), as implied by the "as if by
atoi()": if the digit sequence produces an unrepresentable value,
the behavior is undefined (!). If your users are well-behaved or
the implementation really uses strtol(), the undefined behavior
will not cause trouble.)

At this point, the "%d" directive has either succeeded or failed.

If it fails, the scanf() returns EOF or 0 as appropriate, because
there have been no successful conversions and assignments, and the
stream has or has not encountered an EOF-or-error condition. (An
input error looks exactly like an input EOF to scanf, which -- at
least logically -- just calls fgetc() to get each character. See
fgetc() to see what happens at actual EOF or input error.)

If it succeeds, the scanf() assigns the converted value, and proceeds
on to the next directive, "%*c". This means:
c) DO NOT skip any white space. (This is one of a very few directives
that omits the "skip white space" step.)
d) Read a single character from the stream. This may fail (fgetc
returns EOF) or succeed.
d1) EOF: the directive fails with an input failure.
d2) The directive succeeds. The "*" suppresses assignment and
the character just consumed is discarded.

In case (d1), the scanf() terminates and returns 1, because one
assignment -- the "%d" -- has already completed successfully. In
case (d2), the scanf() proceeds on to the next directive.

There is no next directive (the next format character is '\0'), so
at this point the scanf() completes successfully and returns 1.

Thus, this scanf() call will return one of three possible values:
EOF, 0, or 1. EOF means "EOF (including input error) encountered
before reaching any non-white-space character for the %d directive";
0 means "no digits found, and at most a sign character possibly
consumed and discarded", and 1 means "an integer successfully
converted and assigned, and then either one follow-on character
consumed, or EOF encountered and ignored".

Note that nothing here says that the follow-on character that was
consumed was a newline. Suppose the user entered "123bob". Then
the %d converts the 123, and the %*c eats the first 'b' of "bob",
leaving 'o', the second 'b', and the newline in the input stream.

This is not the only thing that can go wrong. Imagine a user enters
a blank line during part (a) -- what happens then?

Think about things a user might type that might cause trouble in
any of the sub-steps in part (b). Imagine the user enters "three"
instead of the number "3" -- what happens then?

>Incidentally, would it be safe to omit the trailing NULL pointer
>argument, since scanf isn't storing anything through it?

Yes.
--
In-Real-Life: Chris Torek, Berkeley Software Design Inc
El Cerrito, CA, USA Domain: to...@bsdi.com +1 510 234 3167
http://claw.bsdi.com/torek/ (not always up) I report spam to abuse@.

nais...@enteract.com

unread,
Jan 14, 2001, 5:47:34 AM1/14/01
to
Chris Torek <to...@elf.bsdi.com> wrote:
> The first [scanf] directive here is "%d". This means:

[...]

> (There is a caveat in (b3) and (b4), as implied by the "as if by
> atoi()": if the digit sequence produces an unrepresentable value,
> the behavior is undefined (!). If your users are well-behaved or
> the implementation really uses strtol(), the undefined behavior
> will not cause trouble.)

But this has changed to strtol in C99, correct?

--
nais...@enteract.com

Chris Torek

unread,
Jan 14, 2001, 8:15:54 AM1/14/01
to
I wrote:
>>... if the digit sequence [read by %d in a scanf] produces an
>>unrepresentable value, the behavior is undefined (!). ...

In article <93s046$lnu$1...@bob.news.rcn.net> <nais...@enteract.com> asks:


>But this has changed to strtol in C99, correct?

I only have a draft (still, tsk :-) ) but in that draft, the behavior
still appears to be undefined in this case.

The draft *does* use new wording that makes it clear that, in, e.g.:

scanf("%f", &n);

if the input in the stdin stream consists of the sequence:

"-345.6e+oops"

eats the "-345.6e+", leaving "oops" in the stream. At this point
the draft appears to say that since "-345.6e+"

is not a matching sequence, the execution of the directive fails

In other words, after eating "-345.6e+", the "-345.6" part is *not*
treated as a valid floating-point number for "%f", the "e+" *is* gone
from the input stream, and the whole thing fails.

This is really quite horrible, but there you have it. Yet another
reason to use fgets() first, and then do your own parsing. :-)

willem veenhoven

unread,
Jan 14, 2001, 9:50:33 AM1/14/01
to
Chris Torek wrote:
>
> The draft *does* use new wording that makes it clear that, in, e.g.:
>
> scanf("%f", &n);
>
> if the input in the stdin stream consists of the sequence:
>
> "-345.6e+oops"
>
> eats the "-345.6e+", leaving "oops" in the stream. At this point
> the draft appears to say that since "-345.6e+"
>
> is not a matching sequence, the execution of the directive fails
>
> In other words, after eating "-345.6e+", the "-345.6" part is *not*
> treated as a valid floating-point number for "%f", the "e+" *is* gone
> from the input stream, and the whole thing fails.
>
> This is really quite horrible, but there you have it. Yet another
> reason to use fgets() first, and then do your own parsing. :-)

IMHO the fact that the (draft) code implies that a horrible sequence of
events might happen in this case, does not necessarily mean that the
compiler developers will make this scene become reality.

Since I got the feeling that you are involved in implementing the code
in compilers or other primary tools, maybe you can answer the question
how 'undefined behaviour' is handled in practice. Undefined behaviour
isn't something that can be programmed, isn't it?

willem

Chris Torek

unread,
Jan 14, 2001, 7:03:02 PM1/14/01
to
>Chris Torek wrote:
>> The draft *does* use new wording that makes it clear that, in, e.g.:
>>
>> scanf("%f", &n);
>>
>> if the input in the stdin stream consists of the sequence:
>>
>> "-345.6e+oops"
>>
>> eats the "-345.6e+", leaving "oops" in the stream. At this point
>> the draft appears to say that since "-345.6e+"
>>
>> is not a matching sequence, the execution of the directive fails
>>
>> In other words, after eating "-345.6e+", the "-345.6" part is *not*
>> treated as a valid floating-point number for "%f", the "e+" *is* gone
>> from the input stream, and the whole thing fails.
>>
>> This is really quite horrible, but there you have it. Yet another
>> reason to use fgets() first, and then do your own parsing. :-)

In article <3A61BCB9...@veenhoven.com>


willem veenhoven <wil...@veenhoven.com> writes:
>IMHO the fact that the (draft) code implies that a horrible sequence of
>events might happen in this case, does not necessarily mean that the
>compiler developers will make this scene become reality.

If the draft wording survived into the final standard, any compiler
that does *not* fail the scanf, after eating and discarding the
input, is not conformant.

>Since I got the feeling that you are involved in implementing the code
>in compilers or other primary tools, maybe you can answer the question
>how 'undefined behaviour' is handled in practice. Undefined behaviour
>isn't something that can be programmed, isn't it?

First, just for clarity: "undefined behavior" is different from
the "horrible" thing I was talking about above. What I think is
horrible is that fscanf() eats up what *could* be a perfectly
valid floating point number (-345.6). It *could* then stop and
set "n" (a float) to -345.6, leaving "e+oops" in the input stream.
Instead, this C99 draft requires it to keep going, eat the "e+",
and then have the %f conversion fail entirely.

My stdio (written to the C89 wording) pushes the "e+" back into
the stream, and sets n to -345.6. Subsequent fgetc() calls will
return 'e', '+', 'o', 'o', 'p', 's'.

Now, on to what undefined behavior is all about. Undefined behavior
exists for at least two separate reasons:

- it offers implementors a "hook" on which to hang useful,
implementation-specific behavior; and

- it gives implementors an "escape clause" so that they can
get out of doing something overly complex or difficult or
slow.

Consider, for instance, the undefined behavior on signed integer
arithmetic overflow. If you have:

int a, b, c;

a = INT_MAX - 1;
b = INT_MAX - 2;
c = a + b;

the "proper" result for "a+b" is obviously (2*INT_MAX - 3).
Computers, however, are full of implementations in which this result
is not produced by an ordinary "add" instruction inside the machine.
Typically, actual computers do one of several things, or sometimes
more than one of them. They may:

- add the two numbers and completely ignore the overflow, often
producing what in this case will be interpreted as a negative
number as the result stored in "c"; or

- add the two numbers and note the overflow, yet continue on
anyway; or

- abort the "add" instruction with an "overflow trap".

Suppose the C standard said "an integer overflow must always be
caught at runtime". In this case, machines with no automatic
detection would require that the C compiler either (a) prove at
compile time that the sum cannot overflow or (b) include instructions
in the final output code that detect overflow. On a machine with
an "overflow flag", this might not be too bad -- instead of:

add r1, r2

you would just do:

clear_overflow_flag # if needed
add r1, r2
jump_if_overflow_flag_is_set some_label

or maybe:

add_and_set_flags r1, r2 # e.g., "addcc %l0, %l1, %l1" on sparc
trap_if_overflow_flag_is_set # e.g., "tvs" on sparc

But in this case we have at least doubled the number of instructions
required for each add. Those who think their code should go fast,
whether or not the answer is sensible, hate this. :-)

On the other hand, the C standard could define integer overflow
behavior as "integer overflow is always ignored". But in this
case, machines that automatically detect overflow and trap are
penalized. Now their compilers have to emit special code to figure
out whether the overflow might occur, and if so, do some other
calculation instead, to arrive at whatever result is required by
the C standard.

Another example of undefined behavior occurs when using "invalid"
pointers (such as those set to NULL):

char *p = NULL;

use(*p);

On some machines, "*p" happens to work fine, and gives you "the
byte at zero" or whatever. On other machines, "*p" often produces
a runtime trap, and "segmentation fault (core dumped)" or whatever.
By leaving the behavior undefined, the C standard allows an
implementor to "just do whatever the machine does" (i.e., be lazy
and produce faster code; you have to hope that the machine traps
your bugs in its hardware).

This version of undefined behavior does not normally let you do
anything special or neat. Instead, it acts more as a "caveat
emptor": if you write code with undefined behavior, you may have
to study your implementation carefully to figure out what happens.
You are probably better off, in this case, writing some other
code that does not invoke undefined behavior.

The other kind of "undefined behavior" allows implementors to
provide useful extensions. With any luck, these extensions are
even documented. :-) For instance:

#include <sysgraphics.h>

produces "undefined behavior", because <sysgraphics.h> is not a
Standard-defined header. Obviously, though, <sysgraphics.h> is
going to define some set of functions and macros and whatnot that
allow you to do some kind of graphics, probably on a display screen.
This is something you simply cannot do at all in Standard C.

There is, of course, no obligation for the implementor to make the
undefined behavior from <sysgraphics.h> particularly sensible.
Perhaps, for instance, after #include <sysgraphics.h>, the "fopen"
function no longer opens stdio streams, but instead manipulates
the display of a funnel (the "f"). Now "fopen" causes the funnel
to open wider, "frotate" makes it spin, and "fclose" cinches it
down. When you use undefined behavior, you still take the risk
that all your previously well-defined behavior may change. (An
implementor would probably have to be insane to re-define ordinary
file I/O functions like this, but it is not *illegal* in any sense.
Undefined behavior is like a miracle: *anything* can happen,
including no one else on the earth perceiving it. :-) )

On, e.g., an embedded system where "*(struct sysvec *)NULL" happens
to access the system's restart and interrupt vectors, the "undefined
behavior" of following this particular NULL pointer is useful to
the OS-writer. If the C standard *required* C compilers to do
something special with NULL pointers, C would be that much less
useful for OS-writing. So even the kind of undefined behavior that
you should normally avoid like a Roger Corman movie is not always
a bad thing. :-)

David Thompson

unread,
Jan 21, 2001, 4:28:02 PM1/21/01
to
Chris Torek <to...@elf.bsdi.com> wrote :
...
> Now, on to what undefined behavior is all about. ...

> Consider, for instance, the undefined behavior on signed integer
> arithmetic overflow. ...
> Suppose the C standard [required a trap] On a machine with
> an "overflow flag", ...

> But in this case we have at least doubled the number of instructions
> required for each add. Those who think their code should go fast,
> whether or not the answer is sensible, hate this. :-)
>
Or more defensibly those who know enough about
their problem space, constraints, and numerical analysis
to be sure overflow doesn't occur, while the compiler
can't determine this and optimize away the check.
I suspect your set is larger than mine, though. :-{

And as to your earlier question, whether *scanf
%d etc. out-of-range is still undefined like atoi etc.
not strtol etc., according to my (ANSIfied) copy
of C99, 7.19.6.2p10 is unchanged from n869:
>>
... the input item ... is converted to a type appropriate
to the conversion specifier. [or match fails] [and stored]
... or if the result of the conversion cannot be represented
in the object, the behavior is undefined.
<<
and unchanged from C90 7.9.6.2 except it now uses
the more precise "represented in the object" instead of
"represented in the space provided".

Of course, I agree that UB that "just accidentally happens"
to be like strtol etc. is nice. :-}

--
- David.Thompson 1 now at worldnet.att.net

0 new messages