how scanf leaves stdin in case of input error?

A

Alexo

hello everyone,
consider my simple example:

/* code starts here */

#include <stdio.h>

int main(void){

int a = 0, b = 0;

do{
printf("\ninsert an integer number ");
b = scanf("%d", &a);
}while( b != 1 );

printf("\nThe number inserted is %d", a);

return 0;
}

/* code ends here */

try to execute it inputing a character instead of an integer number. The
loop never ends.
I would like that the loop be executed only once when the scanf fails.
Thank you in advance
 
J

John Gordon

In said:
do{
printf("\ninsert an integer number ");
b = scanf("%d", &a);
}while( b != 1 );
try to execute it inputing a character instead of an integer number. The
loop never ends.
I would like that the loop be executed only once when the scanf fails.
Thank you in advance

If you want the input prompt to appear only once, then why did you put
it inside a loop?

Perhaps you want something more like this:

char input_buffer[99];

printf("\ninsert an integer number ");
fflush(stdout);
fgets(input_buffer, sizeof(input_buffer), stdin);
b = sscanf(input_buffer, "%d", &a);
if (b == 1)
printf("\nThe number inserted is %d\n", a);
else
printf("You did not enter a number. Shame on you!\n");
 
E

Eric Sosman

hello everyone,
consider my simple example:

/* code starts here */

#include<stdio.h>

int main(void){

int a = 0, b = 0;

do{
printf("\ninsert an integer number ");
b = scanf("%d",&a);
}while( b != 1 );

printf("\nThe number inserted is %d", a);

return 0;
}

/* code ends here */

try to execute it inputing a character instead of an integer number. The
loop never ends.
I would like that the loop be executed only once when the scanf fails.

This is Question 12.9 in the comp.lang.c Frequently Asked
Questions (FAQ) list at <http://www.c-faq.com/>.
 
A

Alexo

----- Original Message -----
From: "John Gordon" <[email protected]>
Newsgroups: comp.lang.c
Sent: Wednesday, March 09, 2011 11:03 PM
Subject: Re: how scanf leaves stdin in case of input error?

If you want the input prompt to appear only once, then why did you put
it inside a loop?

the loop is required to prompt for each time you give an incorrect answer
Perhaps you want something more like this:

char input_buffer[99];

printf("\ninsert an integer number ");
fflush(stdout);
fgets(input_buffer, sizeof(input_buffer), stdin);
b = sscanf(input_buffer, "%d", &a);
if (b == 1)
printf("\nThe number inserted is %d\n", a);
else
printf("You did not enter a number. Shame on you!\n");

yes that's right! That solves my problem reading directly from stdin qith
scanf.
So the modified dummy program is now:

#include <stdio.h>

#define DIM 256

int main(void)
{
int a = 0, b = 0;
char input_buffer[DIM];

do{
printf("\ninsert an integer number ");
fgets(input_buffer, DIM, stdin);
b = sscanf(input_buffer, "%d", &a);
}while( b != 1 );

printf("\nThe number inserted is %d", a);

return 0;
}
 
B

Ben Bacarisse

Alexo said:
----- Original Message -----
From: "John Gordon" <[email protected]>
Newsgroups: comp.lang.c
Sent: Wednesday, March 09, 2011 11:03 PM
Subject: Re: how scanf leaves stdin in case of input error?

If you want the input prompt to appear only once, then why did you put
it inside a loop?

the loop is required to prompt for each time you give an incorrect answer
Perhaps you want something more like this:

char input_buffer[99];

printf("\ninsert an integer number ");
fflush(stdout);
fgets(input_buffer, sizeof(input_buffer), stdin);
b = sscanf(input_buffer, "%d", &a);
if (b == 1)
printf("\nThe number inserted is %d\n", a);
else
printf("You did not enter a number. Shame on you!\n");

yes that's right!

It's better but not quite right because fgets can fail. That's
particularly a problem when you put it in a loop...
That solves my problem reading directly from stdin qith
scanf.
So the modified dummy program is now:

#include <stdio.h>

#define DIM 256

int main(void)
{
int a = 0, b = 0;
char input_buffer[DIM];

do{
printf("\ninsert an integer number ");
fgets(input_buffer, DIM, stdin);

John Gordon's sizeof input_buffer is (to my mind) better than repeating
DIM here.
b = sscanf(input_buffer, "%d", &a);
}while( b != 1 );

printf("\nThe number inserted is %d", a);

return 0;
}

You have undefined behaviour now when an input error occurs and a
possible infinite loop when the end of the input is detected. I think
it is a good idea to get into the habit of handling these cases[1].

The fundamental problem is that there may be no integer forthcoming (no
matter how much you bug the user with a prompt) and you should start out
by deciding what to do in that situation. One way to settle that is to
say that you'll keep prompting until fgets reports that it can't get any
new data at which point you give up (maybe with a message saying why).

[1] There are cases of undefined behaviour when the input integer causes
and overflow, but I think it is perfectly reasonable to put those to one
side at this stage in learning.
 
B

Ben Bacarisse

new.c does what you want.
I wrote it to help you to get a better understanding of
how scanf leaves stdin in case of input error.

/* BEGIN new.c */

#include <stdio.h>

int
main(void)
{
int a, b;

do {
printf("\ninsert an integer number ");
fflush(stdout);
b = scanf("%d", &a);
if (b == 0) {
do {
a = getchar();
} while (a != '\n' && a != EOF);
puts("That's no good.");
}
} while( b != 1 );
printf("\nThe number inserted is %d\n", a);
return 0;
}

/* END new.c */

You get an infinite loop when the data stream ends with no integer.
 
B

Ben Bacarisse

This version show it better, I think,
especially if you input a few characters of text.
int a, b;

do {
printf("\ninsert an integer number ");
fflush(stdout);
b = scanf("%d", &a);
if (b == 0) {
puts("After that last scanf call, stdin contained");
do {
a = getchar();
printf("%c ");

printf what? If you print a, you need t handle a == EOF.
} while (a != '\n' && a != EOF);

I'd use a while loop:

while ((a = getchar()) != '\n' && a != EOF)
putchar(a);
 
J

James Waldby

From: "John Gordon" ...
If you want the input prompt to appear only once, then why did you put
it inside a loop?

the loop is required to prompt for each time you give an incorrect
answer
Perhaps you want something more like this:
[snip JG code]
yes that's right! That solves my problem reading directly from stdin
qith scanf.
So the modified dummy program is now:

#include <stdio.h>

#define DIM 256

int main(void)
{
int a = 0, b = 0;
char input_buffer[DIM];

do{
printf("\ninsert an integer number "); fgets(input_buffer, DIM,
stdin);
b = sscanf(input_buffer, "%d", &a);
}while( b != 1 );

printf("\nThe number inserted is %d", a);

return 0;
}

When given an empty input file on my system, your program
generates output about 4.5 times faster than Pete's program
[as posted 10 Mar 2011 08:53:24 -0600, with printf("%c ");
changed to printf("%c ",a );].

I ran both of them as below [where ~> represents shell prompt]
and pressed control-C after a few seconds to interrupt execution.
Yours produces ~ 22MB per elapsed second, his gives 4.9MBpes.

~> time ./scanf-alexo < /dev/null > t; wc t

real 0m4.615s
user 0m1.918s
sys 0m2.670s
3854652 15418605 100220928 t

~> time ./scanf-pete < /dev/null > t; wc t

real 0m4.823s
user 0m0.538s
sys 0m4.279s
800711 3202844 20818486 t

~> tail t
insert an integer number
insert an integer number
insert an integer number
insert an integer number
insert an integer number
insert an integer number
insert an integer number
insert an integer number
insert an integer number
insert an integer number
 
T

Tobias Blass

Thank you again.

The strange thing is that
when I tested it on my machine,
it printed a.

This is just coincidence I think. Printf looks for its argument on the stack
(where your parameter is supposed to be) and finds a there.
 
E

Eric Sosman

[...]
It's an interesting instance (interesting to me anyway)
of undefined behavior, because when I tested it
the exhibited behavior matched what I wanted the code to do,
even though I had not written code
to correctly state what I wanted to be done.

Welcome to the programmer's daily existence.

The gap between "What I said" and "What I meant," though both
wide and deep, is easy to overlook. As programmers we all too
frequently find ourselves in the middle of that gap, suspended over
nothing like Wile E. Coyote suddenly realizing he's chased Roadrunner
just a little too far and too recklessly ...
 
J

James Kuyper

I understand.

It's an interesting instance (interesting to me anyway)
of undefined behavior, because when I tested it
the exhibited behavior matched what I wanted the code to do,
even though I had not written code
to correctly state what I wanted to be done.

Don't get too interested in such things; it's just a meaningless
coincidence (though not a highly improbable one). The important thing to
think about is avoiding writing such code, not trying to figure out why
it accidentally worked.
 
T

Tobias Blass

Don't get too interested in such things; it's just a meaningless coincidence
(though not a highly improbable one). The important thing to think about is
avoiding writing such code, not trying to figure out why it accidentally
worked.
I think it _is_ interesting why it works. There's nothing wrong with getting
interested in that. You should not use it in your code, though. But it's also
useful to know becuase there are formatstring exploits that use this behavior.
If you don't know how they work you can't prevent them. It may be enough to say
"Never write printf(user_defined_string), use printf("%s",user_defined_string)"
but I myself would want to know why.
 

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,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top