By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
459,366 Members | 1,364 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 459,366 IT Pros & Developers. It's quick & easy.

Standard input stream behaviour on Linux

P: n/a

I have a fully-portable C program (or at least I think I do). It works
fine on Windows, but malfunctions on Linux. I suspect that there's
something I don't know about the standard input stream that's causing
the problem.

Here's how I wrote the program originally:

#include <stdio.h>
#include <string.h>

void TrimNewLineOffEnd(char *const str)
{
str[strlen(str) - 1] = 0;
}

int main(void)
{
char buf[20];

unsigned age, siblings;

printf("What age are you? ");

scanf("%u",&age);

printf("How many siblings do you have? ");

scanf("%u", &siblings);

printf("What's your name? ");

fgets(buf,15,stdin);

TrimNewLineOffEnd(buf);

printf("\n\nYour name is %s, you're %u years old and you have %u
siblings!\n\n",
buf,age,siblings);

return 0;
}

This original code didn't work as intended on either Windows or Linux.
On both systems, when control reached "fgets", the user wasn't given a
chance to enter their name; instead, fgets returned immediately with
an empty string.

To remedy this problem on Windows, I put in "fflush(stdin)" right
before the call to "fgets". This fixed the problem and the program
worked as intended. This didn't work on Linux however.

What am I doing wrong?
Oct 22 '08 #1
Share this Question
Share on Google+
27 Replies


P: n/a
Tomás Ó hÉilidhe wrote:
>
This original code didn't work as intended on either Windows or Linux.
On both systems, when control reached "fgets", the user wasn't given a
chance to enter their name; instead, fgets returned immediately with
an empty string.

To remedy this problem on Windows, I put in "fflush(stdin)" right
before the call to "fgets". This fixed the problem and the program
worked as intended. This didn't work on Linux however.

What am I doing wrong?
Calling fflush(stdin). fflush only (portably) works on output streams.

--
Ian Collins
Oct 22 '08 #2

P: n/a
Tomás Ó hÉilidhe <t...@lavabit.comwrote:
I have a fully-portable C program (or at least I think I
do). It works fine on Windows, but malfunctions on Linux.
I suspect that there's something I don't know about the
standard input stream that's causing the problem.
You haven't read the FAQ.
Here's how I wrote the program originally:

#include <stdio.h>
#include <string.h>

void TrimNewLineOffEnd(char *const str)
{
* * str[strlen(str) - 1] = 0;
}
This assumes there was a newline.
int main(void)
{
* * char buf[20];
* * unsigned age, siblings;

* * printf("What age are you? ");
You should flush stdout if your prompt doesn't have
a newline.
* * scanf("%u",&age);
You should really check the return value.
* * printf("How many siblings do you have? ");
* * scanf("%u", &siblings);
There is no reason for %u to convert the newline
that follows the entered number.
* * printf("What's your name? ");
* * fgets(buf,15,stdin);
More robust is fgets(buf, sizeof buf, stdin)
* * TrimNewLineOffEnd(buf);
There won't necessarily be a newline on the last line
of input.

<snip>
To remedy this problem on Windows, I put in
"fflush(stdin)"
Google clc for comments on fflush stdin.
right before the call to "fgets". This fixed the
problem and the program worked as intended.
But it wasn't portable.
This didn't work on Linux however.
Undefined behaviour rarely works on all systems.

--
Peter
Oct 22 '08 #3

P: n/a
Tomás Ó hÉilidhe wrote:
I have a fully-portable C program (or at least I think I do). It works
fine on Windows, but malfunctions on Linux. I suspect that there's
something I don't know about the standard input stream that's causing
the problem.

Here's how I wrote the program originally:

#include <stdio.h>
#include <string.h>

void TrimNewLineOffEnd(char *const str)
{
str[strlen(str) - 1] = 0;
}
See the responses by Peter Nilsson and Ian Collins. As Nilsson
points out, this function will lop off the final character whether
it's a newline or not. (And it will *really* misbehave if the
argument is the empty string!) If you want to remove a newline
if present and leave the string unchanged otherwise, and do it
safely, and still insist on doing it with just one line, try

str[ strcspn(str, "\n") ] = '\0';

Honor the Seventh Commandment!

http://www.lysator.liu.se/c/ten-commandments.html

--
Er*********@sun.com
Oct 22 '08 #4

P: n/a
In article <32**********************************@b2g2000prf.g ooglegroups.com>,
Tomas S hIilidhe <to*@lavabit.comwrote:
>
I have a fully-portable C program (or at least I think I do). It works
fine on Windows, but malfunctions on Linux. I suspect that there's
something I don't know about the standard input stream that's causing
the problem.

Here's how I wrote the program originally:
[...]

printf("How many siblings do you have? ");

scanf("%u", &siblings);

printf("What's your name? ");

fgets(buf,15,stdin);
>This original code didn't work as intended on either Windows or Linux.
On both systems, when control reached "fgets", the user wasn't given a
chance to enter their name; instead, fgets returned immediately with
an empty string.

To remedy this problem on Windows, I put in "fflush(stdin)" right
before the call to "fgets". This fixed the problem and the program
worked as intended. This didn't work on Linux however.

What am I doing wrong?
(i) fflushing an input stream. This invokes undefined behavior; its
effect seems to be making it act the way you want on Windows, not
changing anything on Linux, and making daemons fly out of your nose
on the DeathStation.
(ii) Using scanf for user input. The problem you've run into is only
one of the ways it will make your life harder.

When you use scanf to read a number, it leaves everything after the end
of the number (including, in your case, the newline at the end of the
line) in the input buffer.
Then when you call fgets, it reads to the next newline... which happens
to be the one after the number scanf just read, so it doesn't need to
wait for more and returns immediately with an "empty" line.
(If you answered the siblings question with "15 Bob", the program
should conclude that your name is Bob and that you have 15 siblings.)

A better way to read user input is to *always* grab an entire line with
fgets, and then extract the information you want out of that. (sscanf
will do the job, but strtol and friends give you more control and
better error detection if you're trying to extract numbers from
strings.)

Here's an example (untested, not even compiled on my end, for
demonstration purposes only):
========
int read_int(FILE *in)
{
char buf[42];
puts("Enter a number:");
while(fgets(buf,sizeof buf,in))
{
int ret;
/*sscanf returns the number of successful conversions*/
if(sscanf(buf,"%d",&ret)==1)
return ret;
else
puts("I said a NUMBER! Try again:");
}
/*If we fall out of the loop, we got EOF or error*/
puts("Well, fine, then...");
return 0;
}
========
dave

--
Dave Vandervies dj3vande at eskimo dot com
You're right of course. Stupid mistake on my part. That'll teach me
to post while using a rented brain.
--Keith Thompson in comp.lang.c
Oct 22 '08 #5

P: n/a
>I have a fully-portable C program (or at least I think I do).

fflush(stdin) is not portable. Use fflush() only on output streams,
which stdin isn't.
>It works
fine on Windows, but malfunctions on Linux. I suspect that there's
something I don't know about the standard input stream that's causing
the problem.

Here's how I wrote the program originally:

#include <stdio.h>
#include <string.h>

void TrimNewLineOffEnd(char *const str)
{
Warning: the line below malfunctions if strlen(str) is zero.
str[strlen(str) - 1] = 0;
}

int main(void)
{
Do you seriously think 19 characters is enough for a name?
Joseph Ronald Rumplestiltskin VIII
char buf[20];

unsigned age, siblings;

printf("What age are you? ");

scanf("%u",&age);

printf("How many siblings do you have? ");

scanf("%u", &siblings);
What character did you type after the digit(s) indicating the number
of siblings? Newline is a real character. Believe it. Worship it.
And that character is used to respond to the fgets() below.
>
printf("What's your name? ");

fgets(buf,15,stdin);

TrimNewLineOffEnd(buf);

printf("\n\nYour name is %s, you're %u years old and you have %u
siblings!\n\n",
buf,age,siblings);

return 0;
}

This original code didn't work as intended on either Windows or Linux.
On both systems, when control reached "fgets", the user wasn't given a
chance to enter their name; instead, fgets returned immediately with
an empty string.
scanf("%u", &some_var); is supposed to leave the tail end of
the line (newline, maybe a trailing space also) in the input buffer.

I prefer to use fgets() followed by a conversion function like
strtol() or strtoul() or strtod().
>To remedy this problem on Windows, I put in "fflush(stdin)" right
before the call to "fgets". This fixed the problem
Much like putting black tape over the "CHECK ENGINE" light fixes
car problems.
>and the program
worked as intended. This didn't work on Linux however.
Oct 22 '08 #6

P: n/a
On Oct 23, 3:47*am, Peter Nilsson <ai...@acay.com.auwrote:
void TrimNewLineOffEnd(char *const str)
{
* * str[strlen(str) - 1] = 0;
}

This assumes there was a newline.

The Dinkumware online reference tells me that "fgets" puts a new-line
character at the end.

* * printf("What age are you? ");

You should flush stdout if your prompt doesn't have
a newline.

So you mean:

printf("What age are you? "); fflush(stdout);

That right?

There is no reason for %u to convert the newline
that follows the entered number.

So how can I call fgets after I've used scanf to take in a number?
Would the following be fully portable:

scanf("%u",&age);
getchar(); /* Take in the new-line character */
fgets(buf,15,stdin);

Just as an aside, is a new line always represented as a single byte
(i.e. '\n'), coz I've seen how on some systems you have "\r\n".
Oct 22 '08 #7

P: n/a
On Oct 23, 4:02*am, gordonb.od...@burditt.org (Gordon Burditt) wrote:
Much like putting black tape over the "CHECK ENGINE" light fixes
car problems.

Don't be foolish, everyone knows you just disconnect the LED when
you're selling the car :P :P :P
Oct 22 '08 #8

P: n/a
Tomás Ó hÉilidhe wrote:
I have a fully-portable C program (or at least I think I do).
No, it isn't.
It works
fine on Windows, but malfunctions on Linux.
If it works 'fine' on Windows, that is an accident.
I suspect that there's
something I don't know about the standard input stream that's causing
the problem.

Here's how I wrote the program originally:
Here's a modified version. It is nowhere near professional standards,
but it will help you, I think.

#include <stdio.h>
#include <string.h>

/* mha: note the changed body of this function */
void TrimNewLineOffEnd(char *const str)
{
char *nl = strchr(str, '\n');
if (nl)
*nl = 0;
}

int main(void)
{
char buf[20];
unsigned age, siblings;
printf("What age are you? ");
fflush(stdout); /* mha: added */
scanf("%u", &age);

printf("How many siblings do you have? ");
fflush(stdout); /* mha: added */
scanf("%u", &siblings); /* mha: note change */

printf("What's your name? ");
fflush(stdout); /* mha: added */
fgets(buf, 15, stdin);
fgets(buf, sizeof buf, stdin); /* mha: note change */

TrimNewLineOffEnd(buf);

printf
("\n\nYour name is %s, you're %u years old "
"and you have %u siblings!\n\n", buf, age, siblings);

return 0;
}
Oct 22 '08 #9

P: n/a
Tomás Ó hÉilidhe wrote:
On Oct 23, 3:47 am, Peter Nilsson <ai...@acay.com.auwrote:
>>void TrimNewLineOffEnd(char *const str)
{
str[strlen(str) - 1] = 0;
}
This assumes there was a newline.


The Dinkumware online reference tells me that "fgets" puts a new-line
character at the end.
You must have misunderstood. If a new-line character is read, it is
retained. If the line is too long, it is not added.
>
Just as an aside, is a new line always represented as a single byte
(i.e. '\n'), coz I've seen how on some systems you have "\r\n".
Yes. If the OS differentiates between binary and text output, the '\n'
with be modified appropriately

--
Ian Collins
Oct 22 '08 #10

P: n/a
On Oct 23, 4:34*am, Martin Ambuhl <mamb...@earthlink.netwrote:
* * *scanf("%u", &siblings); * * /* mha: note change */

Did you mean to write?:

"%u\n"
Oct 22 '08 #11

P: n/a
Tomás Ó hÉilidhe <to*@lavabit.comwrites:
On Oct 23, 3:47*am, Peter Nilsson <ai...@acay.com.auwrote:
void TrimNewLineOffEnd(char *const str)
{
* * str[strlen(str) - 1] = 0;
}

This assumes there was a newline.


The Dinkumware online reference tells me that "fgets" puts a new-line
character at the end.
Either it's wrong, or you have a weird non-standard version of fgets.

fgets(buf, 15, stdin) could fail to put a newline at the end if (a)
stdin ends in the middle of a line, or (b) stdin contains a line longer
than 15 characters.
* * printf("What age are you? ");

You should flush stdout if your prompt doesn't have
a newline.


So you mean:

printf("What age are you? "); fflush(stdout);

That right?
Right.
>There is no reason for %u to convert the newline
that follows the entered number.


So how can I call fgets after I've used scanf to take in a number?
Would the following be fully portable:

scanf("%u",&age);
getchar(); /* Take in the new-line character */
fgets(buf,15,stdin);
Ah, but the character following the number might not be a newline. The
user might type

22<space><return>

or

22 years old

Someone else suggested reading the entire line with fgets, and using
sscanf or strtol to parse it. This is a good idea in general.
scanf/fscanf are hard to use reliably unless your input is rather
strictly formatted.
Just as an aside, is a new line always represented as a single byte
(i.e. '\n'), coz I've seen how on some systems you have "\r\n".
Yes, that's the C definition of "newline". Systems that use "\r\n"
(e.g. DOS/Windows) will generally silently convert "\r\n" to and from
"\n" when the file is opened in text mode ("t" flag to fopen()). On
such systems it is especially important to get that mode right.
Oct 22 '08 #12

P: n/a

Is there any way of finding out how many character there are
"lingering" in stdin?

If so, I could flush it as follows:

void FlushStdin(void)
{
unsigned i = AmountLingering();

while (i--)
getchar();
}

I could call FlushStdin before I use fgets.
Oct 22 '08 #13

P: n/a
Tomás Ó hÉilidhe wrote:
On Oct 23, 3:47�am, Peter Nilsson <ai...@acay.com.auwrote:
void TrimNewLineOffEnd(char *const str)
{
� � str[strlen(str) - 1] = 0;
}
This assumes there was a newline.


The Dinkumware online reference tells me that "fgets" puts a new-line
character at the end.
fgets() retains a new-line if it reads one. fgets(buf,15,stdin); will
never read more than 14 characters, and if the new-line character
isn't one of those 14 characters, then there won't be a newline
character retained in your buffer. If that is the case,
TrimNewLineOffEnd will replace the 14th character with '\0'.

....
There is no reason for %u to convert the newline
that follows the entered number.


So how can I call fgets after I've used scanf to take in a number?
Would the following be fully portable:

scanf("%u",&age);
One obscure way is to include whitespace at the end of your format
string. That will cause scanf() to read and ignore all following
whitespace characters (including newlines) until it reads the next non-
whitespace character, or until it reaches the end of the file. This
will work, but I wouldn't recommend it.

One of the fundamental problems with fscanf() is that it treats
newlines just like any other whitespace character, whereas humans tend
to see it as a very special character. This tends to produce very non-
intuitive behavior, that is hard to debug, as soon as fscanf() reads a
newline character that it wasn't intended to read.

My preferred approach is to read in an entire line with fgets(), then
parsing it with sscanf(). WIth this approach, when something goes
wrong, it usually stops at a particular line in the input file, which
tends to make it a lot easier to debug. This approach won't work if
you are scanning, for instance, for "Hello world!", and you don't care
if there's a line break between the "Hello" and the "World!". Then
fscanf("Hello world!") does precisely the right thing (but there's
probably a better way to do it that doesn't use fscanf()).
getchar(); /* Take in the new-line character */
fgets(buf,15,stdin);

Just as an aside, is a new line always represented as a single byte
(i.e. '\n'), coz I've seen how on some systems you have "\r\n".
A newline is always represented by a single character in memory.
However, how a newline is represented in a text mode stream is
entirely up to the implementation, and there's a variety of different
methods in actual current use. It can be a single character, or
multiple characters, and those characters don't have to include '\n'
or '\r'; '\0' is used on some systems.

More exotic approachs are also in use. For example, the file might be
stored in fixed-length blocks, with each block containing a count of
the number of characters actually used. if that count is less than the
block's capacity, that indicates that it's the end of a line. The
remaining characters might be null, or might be garbage. When you read
in the last character of a short block, the C stdio functions
translate that by returning a '\n' as the next character; when you
write a '\n', the C stdio functions tell the system to write the block
with a short length.

This is all invisible to you, unless you open the same file in text
mode and in binary mode, or use some other method of looking at the
actual contents of the file.
Oct 22 '08 #14

P: n/a
Tomás Ó hÉilidhe wrote:
>
.... snip ...
>
To remedy this problem on Windows, I put in "fflush(stdin)" right
before the call to "fgets". This fixed the problem and the
program worked as intended. This didn't work on Linux however.

What am I doing wrong?
Using fflush(stdin). fflush on input files is undefined behaviour.

--
[mail]: Chuck F (cbfalconer at maineline dot net)
[page]: <http://cbfalconer.home.att.net>
Try the download section.
Oct 22 '08 #15

P: n/a
Tomás Ó hÉilidhe <to*@lavabit.comwrites:
Is there any way of finding out how many character there are
"lingering" in stdin?

If so, I could flush it as follows:

void FlushStdin(void)
{
unsigned i = AmountLingering();

while (i--)
getchar();
}

I could call FlushStdin before I use fgets.
No, there isn't.

My guess is you're imagining that the user types a number and then a
newline, and that stdin has a buffer that contains these characters. It
might, but it might also contain fewer (if the terminal is in a "raw"
mode, for example), or more (if the user types ahead).

What exactly do you mean by "lingering" and "flush"? I think if you try
to pin down these definitions, you'll find that either it isn't what
you want, it's impossible, or it's easily accomplished another way.

In this case, I think you really want to discard everything until the
end of the line, so you could just do that.

while (getchar() != '\n') ;

This isn't much of an improvement over just calling fgets() in the first
place, however, which IMHO would be less confusing.
Oct 23 '08 #16

P: n/a
Nate Eldredge <na**@vulcan.lanwrites:
[...]
In this case, I think you really want to discard everything until the
end of the line, so you could just do that.

while (getchar() != '\n') ;
[...]

And if getchar() returns EOF before the end of the line?

--
Keith Thompson (The_Other_Keith) ks***@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
Oct 23 '08 #17

P: n/a
Keith Thompson <ks*@sdsc.eduwrites:
Nate Eldredge <na**@vulcan.lanwrites:
[...]
>In this case, I think you really want to discard everything until the
end of the line, so you could just do that.

while (getchar() != '\n') ;
[...]

And if getchar() returns EOF before the end of the line?
Oops.

int c;
while ((c = getchar()) != EOF && c != '\n') ;

Oct 23 '08 #18

P: n/a

Thanks everyone for the helpful replies.

So let me see if I understand, (keep in mind that I want my program to
be fully-portable and to function as intended on every system).

There's a quick-and-dirty solution as follows:

int c;
char buf[20];
unsigned age;

scanf("%u",&age);

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

fgets(buf,sizeof buf,stdin);

Will this *definitely* do what I want it to do on *every* system?
(Forget for the moment that I should be checking the return values to
see whether the input command was successful).

And then there's the clean solution of always using fgets:

char buf[20];
unsigned age;

fgets(buf,sizeof buf,stdin);
age = strtoul(buf);

fgets(buf,sizeof buf,stdin);

And this will work perfectly all the time too, yeah?
Oct 23 '08 #19

P: n/a
On Oct 23, 12:01*am, Tomás Ó hÉilidhe <t...@lavabit.comwrote:
Thanks everyone for the helpful replies.

So let me see if I understand, (keep in mind that I want my program to
be fully-portable and to function as intended on every system).

There's a quick-and-dirty solution as follows:

* * int c;
* * char buf[20];
* * unsigned age;

* * scanf("%u",&age);

* * while ((c = getchar()) != EOF && c != '\n');

* * fgets(buf,sizeof buf,stdin);

Will this *definitely* do what I want it to do on *every* system?
You made it a point to stress definitely and every.

If c == EOF, you may need to check for an error. If so, the stream
may be hosed and no longer useless. If not an error and stdin is
accessing a file, attempting to read after EOF is pointless.
(Forget for the moment that I should be checking the return values to
see whether the input command was successful).

And then there's the clean solution of always using fgets:

* * char buf[20];
* * unsigned age;

* * fgets(buf,sizeof buf,stdin);
* * age = strtoul(buf);

* * fgets(buf,sizeof buf,stdin);

And this will work perfectly all the time too, yeah?
If the first input UINT_MAX, you will never know the value has been
reduced to a modulus. You also don't check it this input is numeric.
Oct 23 '08 #20

P: n/a
On 22 Oct, 23:56, Nate Eldredge <n...@vulcan.lanwrote:
Tomás Ó hÉilidhe <t...@lavabit.comwrites:
<snip>
Just as an aside, is a new line always represented as a single byte
(i.e. '\n'), coz I've seen how on some systems you have "\r\n".

Yes, that's the C definition of "newline". Systems that use "\r\n"
(e.g. DOS/Windows) will generally silently convert "\r\n" to and from
"\n" when the file is opened in text mode ("t" flag to fopen()).
I think they *have* to do that conversion if they want to
be a conformant implementations.
On
such systems it is especially important to get that mode right.
yes

--
Nick Keighley

Oct 23 '08 #21

P: n/a
Tomás Ó hÉilidhe wrote:
On Oct 23, 4:34 am, Martin Ambuhl <mamb...@earthlink.netwrote:
> scanf("%u", &siblings); /* mha: note change */


Did you mean to write?:

"%u\n"
Highly unlikely. This would read and convert the number (or
try to, anyhow), and then would read and read and read and read
and keep on reading until it found something that wasn't a white
space character. It would then leave the non-white character
unread. Result: The user must start typing his name before he
sees the prompt for it.

If you *must* use scanf() for interactive input -- not a
wonderful idea, but chacun à son goût -- it is probably better
to do the white-space skipping before consuming the desired
input item rather than after consuming it.

--
Er*********@sun.com
Oct 23 '08 #22

P: n/a
Barry Schwarz wrote:
[...] the stream
may be hosed and no longer useless. [...]
"No longer useless" -- Something we can all aspire to. ;-)

--
Er*********@sun.com

Oct 23 '08 #23

P: n/a
Nate Eldredge wrote, On 22/10/08 23:56:
Tomás Ó hÉilidhe <to*@lavabit.comwrites:
<snip>
>Just as an aside, is a new line always represented as a single byte
(i.e. '\n'), coz I've seen how on some systems you have "\r\n".

Yes, that's the C definition of "newline". Systems that use "\r\n"
(e.g. DOS/Windows) will generally silently convert "\r\n" to and from
"\n" when the file is opened in text mode ("t" flag to fopen()). On
such systems it is especially important to get that mode right.
The "t" flag is non-standard. That standard specifies that you use the
"b" flag to specify binary and if you don't it is text. When open as
text the implementation is *required* to do the conversion from however
it indicates the new-line to a \n. Note though that *some*
implementations on Windows (I'm specifically thinking of Cygwin as
installed by default) use a text file format that uses just a \n to
indicate the new-line
--
Flash Gordon
If spamming me sent it to sm**@spam.causeway.com
If emailing me use my reply-to address
See the comp.lang.c Wiki hosted by me at http://clc-wiki.net/
Oct 23 '08 #24

P: n/a
On 23 Oct 2008 at 16:41, Eric Sosman wrote:
-- not a wonderful idea, but chacun Ã* son goût --
If you're going to be as pretentious as to litter your posts with
snippets of French, at least do it correctly.

Either "Ã* chacun son goût", or else "chacun a son goût" with no accent.

Oct 23 '08 #25

P: n/a
On Oct 22, 4:37*pm, Tomás Ó hÉilidhe <t...@lavabit.comwrote:
I have a fully-portable C program (or at least I think I do). It works
fine on Windows, but malfunctions on Linux. I suspect that there's
something I don't know about the standard input stream that's causing
the problem.

Here's how I wrote the program originally:

#include <stdio.h>
#include <string.h>

void TrimNewLineOffEnd(char *const str)
{
* * str[strlen(str) - 1] = 0;

}

int main(void)
{
* * char buf[20];

* * unsigned age, siblings;

* * printf("What age are you? ");

* * scanf("%u",&age);

* * printf("How many siblings do you have? ");

* * scanf("%u", &siblings);

* * printf("What's your name? ");

* * fgets(buf,15,stdin);

* * TrimNewLineOffEnd(buf);

* * printf("\n\nYour name is %s, you're %u years old and you have %u
siblings!\n\n",
* * * * * *buf,age,siblings);

* * return 0;

}

This original code didn't work as intended on either Windows or Linux.
On both systems, when control reached "fgets", the user wasn't given a
chance to enter their name; instead, fgets returned immediately with
an empty string.

To remedy this problem on Windows, I put in "fflush(stdin)" right
before the call to "fgets". This fixed the problem and the program
worked as intended. This didn't work on Linux however.

What am I doing wrong?
The problem is the calls to scanf() with the %u conversion specifier
leave the trailing newline from the last entry in the input stream,
which causes fgets() to return immediately.

Let's walk through the code. First you prompt for the age, and then
type in a number (say 42) and hit Return. The scanf() call with the
%u conversion specifier will skip over any leading whitespace, then
read and convert the '4' and '2' characters, stopping at the first non-
numeric character in the input stream, which in this case is the
newline character generated when you hit return. Then you type in the
number of siblings (say 2). Again, the scanf() call will skip over
any leading whitespace (the newline character left over from the last
entry), then read and convert the 2, stopping again at the trailing
newline, which is left in the input stream.

Now here's the problem. You call fgets(), and the first thing it sees
the newline left over from the last input, so it returns immediately
instead of waiting for you to enter a name. Calling scanf() with the
"%s" or "%c" conversion specifiers will have the same effect.

fflush(stdin) won't work, because fflush() is not defined on input
streams. You have to change how you're reading your inputs.

What I've found to work best is to read *everything* as a string using
fgets() (since it will consume the trailing newline), then convert and
assign as necessary. It makes for more robust error handling.

Example (untested - no warranties express or implied):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/**
* Since the integer values we're reading are likely very
* small (most people don't live past 999 years old or have
* more than 999 siblings), we're constraining our input
* buffer to hold 3 characters + a newline character +
* the 0 terminator.
*/
#define BUFSIZE 3

/**
* Get a number from user input
*
* @param prompt [in] -- prompt string
* @param val [out] -- the desired value
* @return int -- 1 if value successfully read and converted
* 0 if error
*/
int getUnsignedValue(const char *prompt, usigned int *val)
{
char inBuf[BUFSIZE]; // input buffer
char *chk; // used for error checking
int ret = 0;

printf("%s : ", prompt);
fflush(stdout);

if (fgets(inBuf, sizeof inBuf, stdin))
{
/**
* first, look for a newline in the buffer; if it's
* not there, then the user entered a string that was
* longer than we were expecting and we need to
* reject it
*/
char *newline = strchr(inBuf, '\n');
if (!newline)
{
/**
* newline not found, string too long. However,
* we don't want to leave garbage in the input
* stream so we'll read until we see the newline
* or until there's a read error
*/
while (!newline && fgets(inBuf, sizeof inBuf, stdin))
{
newline = strchr(inBuf, '\n');
}
fprintf(stderr, "Error -- Input too long\n");
}
else
{
/**
* String was not too long, so we attempt to convert
* it to an unsigned integer using the strtoul()
* function.
*/
*val = (unsigned) strtoul(inBuf, &chk, 10);
if (!isspace(*chk) || *chk != 0)
{
fprintf(stderr, "Error -- input was not a valid number
\n");
}
else
{
ret = 1; // Success! val now contains the converted
value
}
}
}
else
{
fprintf(stderr, "Error occurred while reading from stdin\n");
}

return ret;
}

int main(void)
{
unsigned age, sibs;
char name[15];

if (!getUnsignedValue("What is your age", &age))
{
/* handle error */
}

if (!getUnsignedValue("How many siblings do you have", &sibs))
{
/* handle error */
}

printf("What is your name? ");
fflush(stdout);
if (fgets(name, sizeof name, stdin))
{
char *newline = strchr(name, '\n');
if (newline)
*newline = 0;

printf("Your name is %s, you are %u years old and you have %u
siblings\n");
}
else
{
fprintf(stderr, "Error while reading name from stdin\n");
}

return 0;
}

Obviously there are better ways to structure the code to give the user
opportunities to re-enter data. This is just a quick-n-dirty to show
how much interactive input can suck.
Oct 23 '08 #26

P: n/a
On Oct 23, 5:17*pm, John Bode <jfbode1...@gmail.comwrote:

[snip]
#define BUFSIZE 3
Goddammit, I knew I was going to screw up. Obviously, that 3 should
be a 5.

Grump.
Oct 23 '08 #27

P: n/a
On Oct 23, 5:17*pm, John Bode <jfbode1...@gmail.comwrote:
[snip]
* * * * *printf("Your name is %s, you are %u years old and you have %u
siblings\n");
And the other fuckup. Obviously, there should be three additional
arguments to that call:

printf("Your name is %s, you are %u years old and you have %u
siblings\n",
name, age, sibs);
Oct 23 '08 #28

This discussion thread is closed

Replies have been disabled for this discussion.