scanf behaviour

S

stasgold

Hello.

I maybe reinvent the weel ...
I'm trying to read positive integer number with the help of scanf, if
the input value is not positive number but negaive one zero or char , i
have to reread the input until I get the needed pos. number

I wrote the code , but it doesn't work the way it should : if i put
some char into input, the program goes infinite loop instead of
promting me to enter a new value.

Please shed some light on what is the problem. thanks
Code:
#include <stdio.h>

int main()
{
	int n;
	while (1)
	{
		printf("Please enter positive number \n");
		if ( scanf("%d",&n)!=1 || n<=0 )
		{
			printf("Illegal input!\n");
			continue; 
		}
		
	return 0;
	}
return 1;
}
 
I

Ian Collins

Hello.

I maybe reinvent the weel ...
I'm trying to read positive integer number with the help of scanf, if
the input value is not positive number but negaive one zero or char , i
have to reread the input until I get the needed pos. number

I wrote the code , but it doesn't work the way it should : if i put
some char into input, the program goes infinite loop instead of
promting me to enter a new value.
That's because the conversion fails and the characters remain in the
stream. So the next time scanf is called, the same character sequence is
read.

Add

fflush( stdin );

after your printf.
Please shed some light on what is the problem. thanks
Code:
#include <stdio.h>

int main()
{
	int n;
	while (1)
	{
		printf("Please enter positive number \n");
		if ( scanf("%d",&n)!=1 || n<=0 )
		{
			printf("Illegal input!\n");
			continue; 
		}
		
	return 0;
	}
return 1;
}
 
I

Ian Collins

Richard said:
Ian Collins said:




No, don't do that. The behaviour of fflush on streams open for input is
undefined.
Oops, so it is.

I've always used the Solaris man page definition:

"If stream points to an input stream or an update stream into
which the most recent operation was input, that stream is
flushed if it is seekable and is not already at end-of-file."
 
J

Jens Thoms Toerring

I maybe reinvent the weel ...

No, you just found out that scanf() is not very well suited for
dealing with user input. You're definitely not the first and
won't be the last;-)
I'm trying to read positive integer number with the help of scanf, if
the input value is not positive number but negaive one zero or char , i
have to reread the input until I get the needed pos. number
I wrote the code , but it doesn't work the way it should : if i put
some char into input, the program goes infinite loop instead of
promting me to enter a new value.
Please shed some light on what is the problem. thanks
#include <stdio.h>
int main()

int main( void )

tends to be better since your main() function doesn't take any input.
{
int n;
while (1)
{
printf("Please enter positive number \n");
if ( scanf("%d",&n)!=1 || n<=0 )

The problem you're facing is that when scanf() doesn't find what
it's looking for it won't read anything at all. So if the user inputs
something that doesn't start with a number (with optionally a '+' or
'-' in front of it) it stops immediately and nothing of the users
input gets consumed. When scanf() then tries to read again it still
finds the same characters it couldn't deal with already the last time
and bails out again. Repeat at nauseam...

In this case there are two ways t deal with it. The probably saner
approach is to use a function like fgets() to read the input as a
string, analyze this string for a number you are prepared to accept,
and then to use e.g. strtol() to get at that number (which also allows
to do full error checking).

But since you're here just looking for a positive integer there's
the following alternative:

#include <stdio.h>

int main( void )
{
char buffer[10];
int x ;

do {
printf( "Please enter positive number\n" );
while ( scanf( "%9[^-+0-9]", buffer ) != 0 )
/* empty */ ;
} while ( scanf( "%d", &x ) != 1 || x <= 0 );
printf( "Got %d", x );
return 0;
}

The scanf() in the while loop consumes all non-numerical input
(except '+' and '-') and then the scanf() in the do loops
condition tries to read in an integer. If that works and the
number is positive the loop ends. If not we're back to square
one. But there's still the problem that you can't check if the
user input a number that's too large to be stored in an integer.
Such checks can only be done using the approach mentioned first.
And, of course, it also will return 123 for e.g. "asa%#123.12"
(and leaves everything following the "123" in the input buffer)
- I don't know if that's what you want.

Regards, Jens
 
C

CBFalconer

Jens said:
No, you just found out that scanf() is not very well suited for
dealing with user input. You're definitely not the first and
won't be the last;-)


.... snip code using scanf ...

The scanf() in the while loop consumes all non-numerical input
(except '+' and '-') and then the scanf() in the do loops
condition tries to read in an integer. If that works and the
number is positive the loop ends. If not we're back to square
one. But there's still the problem that you can't check if the
user input a number that's too large to be stored in an integer.
Such checks can only be done using the approach mentioned first.
And, of course, it also will return 123 for e.g. "asa%#123.12"
(and leaves everything following the "123" in the input buffer)
- I don't know if that's what you want.

ONLY be done? See the following. No buffers needed. Improved
since the published version. I am not completely happy with the
treatment of negative values in readxint. Also it needs to be
revised to use longs as the fundamental input mechanism. Note the
GPL licence.

/* ------------------------------------------------- *
* File txtinput.c *
* ------------------------------------------------- */

#include <limits.h> /* xxxx_MAX, xxxx_MIN */
#include <ctype.h> /* isdigit, isblank, isspace */
#include <stdio.h> /* FILE, getc, ungetc */
#include "txtinput.h"

/* For licensing restrictions (GPL) see readme.txt in:
* <http://cbfalconer.home.att.net/download/txtio.zip>
*
* These stream input routines are written so that simple
* conditionals can be used:
*
* if (readxint(&myint, stdin)) {
* do_error_recovery; normally_abort_to_somewhere;
* }
* else {
* do_normal_things; usually_much_longer_than_bad_case;
* }
*
* They allow overflow detection, and permit other routines to
* detect the character that terminated a numerical field. No
* string storage is required, thus there is no limitation on
* the length of input fields. For example, a number entered
* with a string of 1000 leading zeroes will not annoy these.
*
* The numerical input routines *NEVER* absorb a terminating
* char (including '\n'). Thus a sequence such as:
*
* err = readxint(&myint, stdin);
* flushln(stdin);
*
* will always consume complete lines, and after execution of
* readxint a further getc (or fgetc) will return the character
* that terminated the numeric field.
*
* They are also re-entrant, subject to the limitations of file
* systems. e.g interrupting readxint(v, stdin) operation with
* a call to readxwd(wd, stdin) would not be well defined, if
* the same stdin is being used for both calls. If ungetc is
* interruptible the run-time system is broken.
*
* Originally issued 2002-10-07
*
* Revised 2006-01-15 so that unsigned entry overflow (readxwd)
uses the normal C modulo (UINT_MAX + 1) operation. readxwd
still rejects an initial sign as an error.
*/

/* -------------------------------------------------------------
* Skip to non-blank on f, and return that char. or EOF The next
* char that getc(f) will return is unknown. Local use only.
*/
static int ignoreblks(FILE *f)
{
int ch;

do {
ch = getc(f);
} while ((' ' == ch) || ('\t' == ch));
/* while (isblank(ch)); */ /* for C99 */
return ch;
} /* ignoreblks */

/*--------------------------------------------------------------
* Skip all blanks on f. At completion getc(f) will return
* a non-blank character, which may be \n or EOF
*
* Skipblks returns the char that getc will next return, or EOF.
*/
int skipblks(FILE *f)
{
return ungetc(ignoreblks(f), f);
} /* skipblks */

/*--------------------------------------------------------------
* Skip all whitespace on f, including \n, \f, \v, \r. At
* completion getc(f) will return a non-blank character, which
* may be EOF
*
* Skipwhite returns the char that getc will next return, or EOF.
*/
int skipwhite(FILE *f)
{
int ch;

do {
ch = getc(f);
} while (isspace(ch));
return ungetc(ch, f);
} /* skipwhite */

/*--------------------------------------------------------------
* Read an unsigned value. Signal error for overflow or no
* valid number found. Returns 1 for error, 0 for noerror, EOF
* for EOF encountered before parsing a value.
*
* Skip all leading blanks on f. At completion getc(f) will
* return the character terminating the number, which may be \n
* or EOF among others. Barring EOF it will NOT be a digit. The
* combination of error, 0 result, and the next getc returning
* \n indicates that no numerical value was found on the line.
*
* If the user wants to skip all leading white space including
* \n, \f, \v, \r, he should first call "skipwhite(f);"
*
* Peculiarity: This specifically forbids a leading '+' or '-'.
*/
int readxwd(unsigned int *wd, FILE *f)
{
unsigned int value, digit;
int status;
int ch;

#define UWARNLVL (UINT_MAX / 10U)
#define UWARNDIG (UINT_MAX - UWARNLVL * 10U)

value = 0; /* default */
status = 1; /* default error */

ch = ignoreblks(f);

if (EOF == ch) status = EOF;
else if (isdigit(ch)) status = 0; /* digit, no error */

while (isdigit(ch)) {
digit = ch - '0';
if ((value > UWARNLVL) ||
((UWARNLVL == value) && (digit > UWARNDIG))) {
status = 1; /* overflow */
value -= UWARNLVL;
}
value = 10 * value + digit;
ch = getc(f);
} /* while (ch is a digit) */

*wd = value;
ungetc(ch, f);
return status;
} /* readxwd */

/*--------------------------------------------------------------
* Read a signed value. Signal error for overflow or no valid
* number found. Returns true for error, false for noerror. On
* overflow either INT_MAX or INT_MIN is returned in *val.
*
* Skip all leading blanks on f. At completion getc(f) will
* return the character terminating the number, which may be \n
* or EOF among others. Barring EOF it will NOT be a digit. The
* combination of error, 0 result, and the next getc returning
* \n indicates that no numerical value was found on the line.
*
* If the user wants to skip all leading white space including
* \n, \f, \v, \r, he should first call "skipwhite(f);"
*
* Peculiarity: an isolated leading '+' or '-' NOT immediately
* followed by a digit will return error and a value of 0, when
* the next getc will return that following non-digit. This is
* caused by the single level ungetc available.
*/
int readxint(int *val, FILE *f)
{
unsigned int value;
int status, negative;
int ch;

*val = value = 0; /* default */
status = 1; /* default error */
negative = 0;

ch = ignoreblks(f);

if (EOF != ch) {
if (('+' == ch) || ('-' == ch)) {
negative = ('-' == ch);
ch = ignoreblks(f); /* absorb any sign */
}

if (isdigit(ch)) { /* digit, no error */
ungetc(ch, f);
status = readxwd(&value, f);
ch = getc(f); /* This terminated readxwd */
}

if (0 == status) {
/* got initial digit and no readxwd overflow */
if (!negative && (value <= INT_MAX))
*val = value;
else if (negative && (value < UINT_MAX) &&
((value - 1) <= -(1 + INT_MIN)))
*val = -value;
else { /* overflow */
status = 1; /* do whatever the native system does */
if (negative) *val = -value;
else *val = value;
}
}
else if (negative) *val = -value;
else *val = value;
}
ungetc(ch, f);
return status;
} /* readxint */

/*-----------------------------------------------------
* Flush input through an end-of-line marker inclusive.
*/
void flushln(FILE *f)
{
int ch;

do {
ch = getc(f);
} while (('\n' != ch) && (EOF != ch));
} /* flushln */

/* End of txtinput.c */
 
J

Jens Thoms Toerring

ONLY be done? See the following. No buffers needed. Improved
since the published version. I am not completely happy with the
treatment of negative values in readxint. Also it needs to be
revised to use longs as the fundamental input mechanism. Note the
GPL licence.

<code snipped>

Thanks for the correction. What I actually wanted to warn the OP
about is that using only scanf() it's impossible to do full error
checking. As you have shown, using fgets() isn't the only method
that can be used here. And your code is a good example of what one
have to take into account to get it RIGHT;-) Unfortunately, when
one has to read in floating point numbers it probably will become
very difficult (or impossible?) to work with a single look-ahead
character and I guess then some solution involving fgets() will be
necessary. Or do you have a nice function also for that case?

Regards, Jens
 
C

CBFalconer

Jens said:
.... snip ...


<code snipped>

Thanks for the correction. What I actually wanted to warn the OP
about is that using only scanf() it's impossible to do full error
checking. As you have shown, using fgets() isn't the only method
that can be used here. And your code is a good example of what one
have to take into account to get it RIGHT;-) Unfortunately, when
one has to read in floating point numbers it probably will become
very difficult (or impossible?) to work with a single look-ahead
character and I guess then some solution involving fgets() will be
necessary. Or do you have a nice function also for that case?

It can be built around the routines I showed. The only problem is
with exponential notation, otherwise 1 char lookahead does fine.
The problem case is: "1.23ex" which requires 2 char lookahead, or 2
level ungetc. scanf suffers from this too.
 
S

stasgold

Thanks for reply.
I give it I try.
If this method is working only on flavours of unix , it's fine with me.
 
S

stasgold

Thanks Jens .

I have to use scanf() because of my task's constrains.
Your work-around works fine, if "fflush(stdin)" idea from previuos post
would fail,
i would surely go for it.

Thanks for the tip.
I maybe reinvent the weel ...

No, you just found out that scanf() is not very well suited for
dealing with user input. You're definitely not the first and
won't be the last;-)
I'm trying to read positive integer number with the help of scanf, if
the input value is not positive number but negaive one zero or char , i
have to reread the input until I get the needed pos. number
I wrote the code , but it doesn't work the way it should : if i put
some char into input, the program goes infinite loop instead of
promting me to enter a new value.
Please shed some light on what is the problem. thanks
#include <stdio.h>
int main()

int main( void )

tends to be better since your main() function doesn't take any input.
{
int n;
while (1)
{
printf("Please enter positive number \n");
if ( scanf("%d",&n)!=1 || n<=0 )

The problem you're facing is that when scanf() doesn't find what
it's looking for it won't read anything at all. So if the user inputs
something that doesn't start with a number (with optionally a '+' or
'-' in front of it) it stops immediately and nothing of the users
input gets consumed. When scanf() then tries to read again it still
finds the same characters it couldn't deal with already the last time
and bails out again. Repeat at nauseam...

In this case there are two ways t deal with it. The probably saner
approach is to use a function like fgets() to read the input as a
string, analyze this string for a number you are prepared to accept,
and then to use e.g. strtol() to get at that number (which also allows
to do full error checking).

But since you're here just looking for a positive integer there's
the following alternative:

#include <stdio.h>

int main( void )
{
char buffer[10];
int x ;

do {
printf( "Please enter positive number\n" );
while ( scanf( "%9[^-+0-9]", buffer ) != 0 )
/* empty */ ;
} while ( scanf( "%d", &x ) != 1 || x <= 0 );
printf( "Got %d", x );
return 0;
}

The scanf() in the while loop consumes all non-numerical input
(except '+' and '-') and then the scanf() in the do loops
condition tries to read in an integer. If that works and the
number is positive the loop ends. If not we're back to square
one. But there's still the problem that you can't check if the
user input a number that's too large to be stored in an integer.
Such checks can only be done using the approach mentioned first.
And, of course, it also will return 123 for e.g. "asa%#123.12"
(and leaves everything following the "123" in the input buffer)
- I don't know if that's what you want.

Regards, Jens
 
R

Richard Heathfield

(e-mail address removed) said:
Thanks for reply.
I give it I try.

Why? It's wrong, broken, wrong, broken, wrong, wrong, wrong! Why would you
use a wrong, broken, wrong, broken, wrong, wrong, wrong suggestion? Even
the guy who suggested it now realises it's wrong, broken, wrong, broken,
wrong, wrong, wrong.
 
S

stasgold

I've tested "fflush( stdin );" on both WinXP and SunOS boxes it works
without a glitch.
 
I

Ian Collins

Richard said:
(e-mail address removed) said:




Why? It's wrong, broken, wrong, broken, wrong, wrong, wrong! Why would you
use a wrong, broken, wrong, broken, wrong, wrong, wrong suggestion? Even
the guy who suggested it now realises it's wrong, broken, wrong, broken,
wrong, wrong, wrong.
Just curious, has fflush( stdin ) always been undefined, rather than
implementation defined? The Solaris version makes sense to me.
 
R

Richard Heathfield

(e-mail address removed) said:
I've tested "fflush( stdin );" on both WinXP and SunOS boxes it works
without a glitch.

Try driving on the wrong side of the road. That works without a glitch too -
mostly.
 
R

Richard Heathfield

Ian Collins said:

Just curious, has fflush( stdin ) always been undefined, rather than
implementation defined?
Yes.

The Solaris version makes sense to me.

How can flushing input make sense? You flush toilets, not taps.
 
I

Ian Collins

Richard said:
(e-mail address removed) said:




Try driving on the wrong side of the road. That works without a glitch too -
mostly.
Especially if the signs tell you to! The operation may well be defined
in XP as it is on Solaris.
 
R

Richard Heathfield

Ian Collins said:
Especially if the signs tell you to!

The "signs", in C, are written in the Standard, and the Standard tells you
not to. There is, in any case, a perfectly portable way to achieve the
objective without invoking undefined behaviour.
The operation may well be defined in XP as it is on Solaris.

That doesn't render the operation *correct*, though. Why not just do it
right, instead of finding excuses to do it wrong?
 
R

Richard Heathfield

Ian Collins said:
Well you flush beer pumps to clear out the stale beer.
^^^

Flushing is an output operation. The Standard makes this clear. So does your
claim about beer pumps, albeit non-normatively.
 
S

stasgold

Richard , with all respect

U haven't provided any reference for your claim that fflush( stdin ) is
undefined, so for me
they are still only your words and not "The Standard"

Read this, for example :
<>
Standard C Library Functions fflush(3C)

NAME
fflush - flush a stream

SYNOPSIS
#include <stdio.h>

int fflush(FILE *stream);

DESCRIPTION
If stream points to an output stream or an update stream in
which the most recent operation was not input, fflush()
causes any unwritten data for that stream to be written to
the file, and the st_ctime and st_mtime fields of the under-
lying file are marked for update.

If stream is a null pointer, fflush() performs this flushing
action on all streams for which the behavior is defined
above. Additionally, an input stream or an update stream
into which the most recent operation was input is also
flushed if it is seekable and is not already at end-of-file.
Flushing an input stream discards any buffered input and
adjusts the file pointer such that the next input operation
accesses the byte after the last one read. A stream is
seekable if the underlying file is not a pipe, FIFO, socket,
or TTY device. An input stream, seekable or non-seekable,
can be flushed by explicitly calling fflush() with a non-
null argument specifying that stream.
<>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,189
Members
46,734
Latest member
manin

Latest Threads

Top