Basic noob question re console input

R

Robbie Brown

It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");
num = scanf("%d", &choice);
while(c = getchar() != '\n');
}
while(num == 0);

return choice;
}

This code ignores characters and multiple integers and simply fetches
the first integer if there is one, embedded integers are ignored and
discarded, the only time an integer is returned is if it's the first
item in the stream.

What are your opinions of this code. This must be such a common
requirement that it has been solved countless times in the past.
 
J

James Kuyper

It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin ...

fflush() "... causes any unwritten data for that stream to be delivered
to the host environment to be written to the file;" (7.21.5.2p2) What
exactly did you expect the behavior would be on an input-only stream?
With only the above description you might conclude that flush(stdin) is
a no-op, since there is not, and never will be, any unwritten data in
the buffer. However, elsewhere in the same clause the behavior in that
case is explicitly specified to be undefined. It's also undefined for an
input-output stream if the most recent operation performed on that
stream was input.
... when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");
num = scanf("%d", &choice);
while(c = getchar() != '\n');
}
while(num == 0);

return choice;
}

This code ignores characters and multiple integers and simply fetches
the first integer if there is one,

That code is written to ignore characters - scanf("%d") returns 0 if the
next non-white-space character isn't a digit. If you don't want it to
ignore characters, but instead to treat them as errors, one alternative
is to retrieve one character at a time, and check for isdigit() and
isspace(). You'll have to assemble the digits into a buffer, and pass
that to sscanf() or strtod() to determine the corresponding integer,
when the buffer is ready.
embedded integers are ignored and
discarded, the only time an integer is returned is if it's the first
item in the stream.

What are your opinions of this code. This must be such a common
requirement that it has been solved countless times in the past.

Unfortunately, while it has been solved, there's no guaranteed portable
C solution. All C streams have a buffering mode; what you want is
unbuffered mode (_IONBF). It's up to each implementation what kind of
buffering stdin has; it's often line-buffered, which means that no data
it received by the program until a new-line has been typed. The behavior
you describe is consistent with line-buffering.

You can change the mode to _IONBF using setvbuf(), but it is
implementation-defined what kinds of changes you can make. You might or
might not be able to make that change to stdin.

Two different functions, both named getch(), but with different
interfaces, are available on two different operating systems that I'm
familiar with for dealing with this problem. Look for getch() in the
documentation for your system. Keep in mind that use of this function
restricts the portability of your code to those systems that support it
(with the same interface).
 
E

Eric Sosman

It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");

`fflush(stdout);' would be a good idea here. See Question 12.4
on the comp.lang.c Frequently Asked Questions (FAQ) page at
num = scanf("%d", &choice);
while(c = getchar() != '\n');

You're not doing anything with `c' (which will be assigned
either 0 or 1), so why bother with it?

More seriously, if getchar() returns EOF (indicating end-of-
input or an error), you'll be stuck here forever ...
}
while(num == 0);

scanf() itself can also return EOF if there's an input error
(as opposed to a non-integer input), in which case `num' will be
non-zero but `choice' won't contain anything useful.
return choice;
}

This code ignores characters and multiple integers and simply fetches
the first integer if there is one, embedded integers are ignored and
discarded, the only time an integer is returned is if it's the first
item in the stream.

What are your opinions of this code. This must be such a common
requirement that it has been solved countless times in the past.

The general feeling is that scanf() is very difficult to use
with interactive input, so difficult that it's probably not worth
trying. FAQ 12.20 has a fuller explanation, and suggestions for
alternative approaches.
 
B

Ben Bacarisse

Robbie Brown said:
I have the following code

I'll try not to duplicate remarks but I think some thing remain to be
said...
int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");
num = scanf("%d", &choice);
while(c = getchar() != '\n');

You are missing parentheses. This repeatedly sets c to 0 or 1 depending
on whether the character read is '\n' or not. You meant:

int c; /* Explained already */
while ((c = getchar()) != '\n');

You see I've also moved the declaration of c. I like to give names the
very smallest scope. If I were forced to used a compiler that did not
allow mixed declarations and statements (it's in C >= C99), I'd still
move it to after the 'do {' line.
}
while(num == 0);

return choice;
}

<snip>
 
K

Keith Thompson

Robbie Brown said:
It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

Should be `int c;`; see below.
do{
printf("%s", "Enter an integer >> ");

As Eric mentioned, adding `fflush(stdout);` here is not a bad idea
(though it's moderately likely to work without it).
num = scanf("%d", &choice);

A more descriptive name than "num" would help, perhaps "items".
while(c = getchar() != '\n');

You're missing parentheses and a check against EOF.

EOF is the reason that c needs to be an int rather than a char.
getchar() returns *either* the character just read, treated as an
unsigned char and then converted to int, *or* EOF, which is a negative
value (and therefore not the same as any valid unsigned char value).

So the above should be:

while ((c = getchar()) != '\n' && c != EOF);
}
while(num == 0);

while (num != 1);
return choice;
}

This code ignores characters and multiple integers and simply fetches
the first integer if there is one, embedded integers are ignored and
discarded, the only time an integer is returned is if it's the first
item in the stream.

Or if it's preceded by whitespace.

scanf("%d", ...) skips any leading whitespace (including newlines),
then reads a decimal integer optionally preceded by '-' or '+'.
Any non-digits are left in the input stream. If it's able to read
an integer, it returns 1 (more generally the number of items read).
If the input doesn't contain a valid integer, it returns 0 (no
items read). If there's another error, it returns EOF.
 
K

Keith Thompson

Robbie Brown said:
It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

One more thing: Make this:

int getChoice(void) {

The empty parentheses are an obsolescent form of function definition.
In the form you've written, it correctly says that getChoice has no
parameters, but not that callers should call it with no arguments.
If you called

n = getChoice("superfluous argument");

the compiler would not complain, and the behavior would be undefined.

(Empty parentheses are valid in C++, and have a different meaning.)
 
M

Miquel van Smoorenburg

It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

Why not something like

int getChoice() {
char buf[128];
char *endptr;
int r;

do {
printf("%s", "Enter an integer >> ");
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("EOF\n");
return -1;
}
r = (int)strtol(buf, &endptr, 10);
} while (*endptr != '\r' && *endptr != '\n');

return r;
}

Mike.
 
K

Kaz Kylheku

It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");
num = scanf("%d", &choice);
while(c = getchar() != '\n');
}
while(num == 0);

return choice;
}

Any situation in which scanf is used for user input is a poor approach.
Consider buffering an entire line of input from the user using fgets
and then using sscanf or other approaches to extract tokens.

You're neglecting to check the return value of scanf to see how many
conversions were successful.

You're neglecting to check the return value of getchar for EOF.

Your program will get into an infinite loop if the user invokes EOF
from the terminal.

The type char is not suitable for capturing the return value of getchar. The
getchar function returns int, so you're invoking a potentially data-losing
conversion which prodices an implementation-defined result.

getchar can return values in the range 0 to UCHAR_MAX, plus the special
value EOF which is some negative number.

There is no requirement that the value of EOF fits into the type char.
(For that matter, the plain char type need not be a signed type.)
This code ignores characters and multiple integers and simply fetches
the first integer if there is one, embedded integers are ignored and

Due to the neglect of the return value of scanf, it also thinks it fetches the
first integer if there *isn't* one.

scanf is not particularly robust. If you scan a %d, but the input doesn't fit
into an int, there is no error checking, just undefined behavior. The atoi
function has the same problem.
 
E

Eric Sosman

It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");
num = scanf("%d", &choice);
while(c = getchar() != '\n');
}
while(num == 0);

return choice;
}
[...]

Due to the neglect of the return value of scanf, it also thinks it fetches the
first integer if there *isn't* one.

The do..while catches matching failures, although it doesn't
handle actual I/O errors or end-of-input correctly.
 
K

Keith Thompson

Miquel van Smoorenburg said:
It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

Why not something like

int getChoice() {
char buf[128];
char *endptr;
int r;

do {
printf("%s", "Enter an integer >> ");
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("EOF\n");
return -1;
}
r = (int)strtol(buf, &endptr, 10);
} while (*endptr != '\r' && *endptr != '\n');

return r;
}

There's no need to check for '\r' (unless you need to handle "foreign"
files, such as Windows-format text files on a UNIX system). Text input
translates native line endings to '\n'.
 
K

Kaz Kylheku

The do..while catches matching failures, although it doesn't
handle actual I/O errors or end-of-input correctly.

Not to mention that it asks the user for an integer, but rejects zero.
 
B

BartC

Robbie Brown said:
It's been many (many) years since I had to write a console application.
I want to be able to get a single integer from a console prompt.
I initially thought I needed to flush stdin when I had what I wanted but
this is apparently a big no no, so I hacked it.

I have the following code

int getChoice(){

int num = 0, choice = 0;
char c;

do{
printf("%s", "Enter an integer >> ");
num = scanf("%d", &choice);
while(c = getchar() != '\n');
}
while(num == 0);

return choice;
}
What are your opinions of this code. This must be such a common
requirement that it has been solved countless times in the past.

I long ago decided to deal only with line-oriented input for this sort of
task. So:

- Read one line of text into a string buffer. The user needs to press Enter
to complete the line (for more interaction, you need a different approach).

- Read actual input from this buffer. If it hits end-of-string, it might
return 0 if trying to read a number, or "" if reading a name or string.
(Note that fgets() might put a newline at the end; I would strip this out as
it just gets in the way.)

The point is that once it's in a string, then you can forget about the
intricacies of C input processing, and use any string processing techniques
to extract the data. (Someone mentioned sscanf(), but even atoi() will do.)

However, there has been a lot of discussion here in the past about the best
way to write a 'getline' function, because stdin could come from a file (or
you could use this to read from files), and a file could potentially contain
one line with billions of characters - it gets complicated. I would
completely ignore that problem and just use a hard limit for line length to
keep things simple. (I've used such limits for several decades with little
mishap.)
 
B

Ben Bacarisse

Kaz Kylheku said:
Not to mention that it asks the user for an integer, but rejects zero.

Are you sure? The code a couple of messages up does not seem to reject
zero.
 
R

Robbie Brown

Not to mention that it asks the user for an integer, but rejects zero.

It doesn't reject 0, 0 is a valid input and is currently used to exit
the application
 
K

Kaz Kylheku

It doesn't reject 0, 0 is a valid input and is currently used to exit
the application

You're right; I got mixed up between num and choice when looking
at the code a second time.
 
K

Kaz Kylheku

It doesn't reject 0, 0 is a valid input and is currently used to exit
the application

By the way, it's necessary to check the return value of -1, not only 0:

switch(scanf(<single item string>, &whatever)) {
case -1: ... /* EOF before any conversion was made */
case 0: ... /* no conversion was made */
case 1: ... /* one conversion was made */
}

Consider this program:

#include <stdio.h>

int main()
{
int d;
printf("%d\n", scanf("%d", &d));
return 0;
}

Test runs on a Linux machine. (Invisible TTY input shown in square brackets):

$ ./a.out
[Ctrl-D][Enter]
-1

$ ./a.out
abc
0

$ ./a.out
42
1

As you can see, if scanf is not able to read a single character in converting
%d, it returns -1. If there is junk, then this counts as a botched conversion
and the count of successful conversions is returned.
 
R

Robbie Brown

It's been many (many) years since I had to write a console application.
(snip)

What are your opinions of this code. This must be such a common
requirement that it has been solved countless times in the past.

First of all thanks for all the thoughts and ideas regarding this problem
I have come up with a solution.
I fully expect to have missed all sorts of things but it works perfectly
as far as I can tell on 64 bit Linux/gcc/c99

The requirements are as follows
input int >> foo //fail, try again
input int >> foobarbaz //fail, try again
input int >> f65oo //fail, try again
input int >> 9 //success, returns 9
input int >> 56tr //success, returns 56
input int >> q 34 fsdd //fail, try again
input int >> 999 //success, return 999
input int >> 9999 //success, return 999
input int >> 0 //success, return 0
input int >> 999xyz //success, return 999
input int >> 999 //success, return 99
input int >> 999 //success, return 9
input int >> 999 //fail, try again
etc etc

so, allow user to enter an int from 0 to 999
ignore larger numbers and trailing non digits
and input with 1 or more non-digit prefix.

The problem has been the data remaining on the input stream
after a failure, the following solves that problem in a single line
I have included fflush(stdout) but I don't understand why I need it yet,
that's my next lesson.

int getChoice2(void){

int BUFSIZE = 4;
char buffer[BUFSIZE];
int count = 0, choice = 0;

while(count == 0 || count == EOF){

printf("%s", "Input an integer >> ");
fflush(stdout);
if(NULL != fgets(buffer, BUFSIZE, stdin)){
count = sscanf(buffer, "%d", &choice);
stdin[0]._IO_read_ptr = stdin[0]._IO_read_end;
}
}

return choice;
}


calm down, it's C, "you can do anything you want" :)
 
E

Eric Sosman

It's been many (many) years since I had to write a console application.
(snip)

What are your opinions of this code. This must be such a common
requirement that it has been solved countless times in the past.

First of all thanks for all the thoughts and ideas regarding this problem
I have come up with a solution.
I fully expect to have missed all sorts of things but it works perfectly
as far as I can tell on 64 bit Linux/gcc/c99

The requirements are as follows
[...]
input int >> 999 //success, return 99
input int >> 999 //success, return 9
input int >> 999 //fail, try again

I don't see why you expect (or want) three different outcomes
for three almost-identical inputs. In any case, the code you post
will give the same outcome ("success, return 999") for all three
and will not treat them differently.
I have included fflush(stdout) but I don't understand why I need it yet,
that's my next lesson.

Did you read the FAQ you were referred to? Question 12.4.
int getChoice2(void){

int BUFSIZE = 4;
char buffer[BUFSIZE];
int count = 0, choice = 0;

while(count == 0 || count == EOF){

printf("%s", "Input an integer >> ");
fflush(stdout);
if(NULL != fgets(buffer, BUFSIZE, stdin)){
count = sscanf(buffer, "%d", &choice);
stdin[0]._IO_read_ptr = stdin[0]._IO_read_end;

This might work on some C implementations some of the time,
but it is not guaranteed to work on all implementations, nor even
all of the time on yours. If you want to get rid of unconsumed
input reliably, you need to consume it -- i.e., read it.
calm down, it's C, "you can do anything you want" :)

Subject to your employer's right to fire your sorry ass for
foolishness, of course. (Not even the self-employed are safe
from such sanctions.)

When I was young and dumb I thought it was clever to write
clever code. Now that I'm old and battle-scarred, I know that
writing clever code is one of the stupider things one can do.
Think upon this, Grasshopper.
 
E

Eric Sosman

[...]
The requirements are as follows
[...]
input int >> 999 //success, return 99
input int >> 999 //success, return 9
input int >> 999 //fail, try again

I don't see why you expect (or want) three different outcomes
for three almost-identical inputs. In any case, the code you post
will give the same outcome ("success, return 999") for all three
and will not treat them differently.

Oh, ratz: I looked at the wrong piece of code: The revised
code you posted will in fact treat them differently -- although
the "Why would you want this?" still remains on open question.

Also: In the revised version, the loop

.... keeps on trying after end-of-input. That may well leave you
stuck forever should end-of-input (or I/O error) actually occur.
 
R

Robbie Brown

snip


Did you read the FAQ you were referred to? Question 12.4.

Yes, of course, but I don't understand *exactly* why it's required, as
in, I haven't seen for myself, but I'll get there.
int getChoice2(void){

int BUFSIZE = 4;
char buffer[BUFSIZE];
int count = 0, choice = 0;

while(count == 0 || count == EOF){

printf("%s", "Input an integer >> ");
fflush(stdout);
if(NULL != fgets(buffer, BUFSIZE, stdin)){
count = sscanf(buffer, "%d", &choice);
stdin[0]._IO_read_ptr = stdin[0]._IO_read_end;

This might work on some C implementations some of the time,
but it is not guaranteed to work on all implementations, nor even
all of the time on yours. If you want to get rid of unconsumed
input reliably, you need to consume it -- i.e., read it.

Well I can't comment on that as I only have a Linux box here. I think I
have an old Windows machine around somewhere, maybe I'll dig it out
although the processor architecture is likely the same.

Anyway, if you look at how

getchar();
fgetc();
scanf();

and

fgets();

work they all work by advancing the read pointer if they successfully
read one or or more characters, advancing the read pointer 'by hand'
simply simulates a read ... it's just much, much faster. I'm currently
investigating what happens to the pushback pointer etc
Subject to your employer's right to fire your sorry ass for
foolishness, of course. (Not even the self-employed are safe
from such sanctions.)

When I was young and dumb I thought it was clever to write
clever code. Now that I'm old and battle-scarred, I know that
writing clever code is one of the stupider things one can do.
Think upon this, Grasshopper.

Yes master ... but keep grasping those pebbles, they'll be in my hand
before you know it :)
 

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

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top