Standard input stream behaviour on Linux

  • Thread starter Tomás Ó hÉilidhe
  • Start date
T

Tomás Ó hÉilidhe

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?
 
I

Ian Collins

Tomás Ó hÉilidhe said:
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.
 
P

Peter Nilsson

Tomás Ó hÉilidhe said:
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.

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.
 
D

dj3vande

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
 
T

Tomás Ó hÉilidhe

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 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".
 
T

Tomás Ó hÉilidhe

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
 
M

Martin Ambuhl

Tomás Ó hÉilidhe said:
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;
}
 
I

Ian Collins

Tomás Ó hÉilidhe said:
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
 
N

Nate Eldredge

Tomás Ó hÉilidhe said:
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.
So you mean:

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

That right?
Right.



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.
 
T

Tomás Ó hÉilidhe

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.
 
J

jameskuyper

Tomás Ó hÉilidhe said:
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'.

....
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.
 
C

CBFalconer

Tomás Ó hÉilidhe said:
.... 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.
 
N

Nate Eldredge

Tomás Ó hÉilidhe said:
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.
 
K

Keith Thompson

Nate Eldredge said:
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?
 
N

Nate Eldredge

Keith Thompson said:
Nate Eldredge said:
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') ;
 
T

Tomás Ó hÉilidhe

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?
 
B

Barry Schwarz

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.
 
N

Nick Keighley

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
 
F

Flash Gordon

Nate Eldredge wrote, On 22/10/08 23:56:
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
 

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,997
Messages
2,570,241
Members
46,831
Latest member
RusselWill

Latest Threads

Top