Standard input stream behaviour on Linux

Discussion in 'C Programming' started by Tomás Ó hÉilidhe, Oct 22, 2008.

  1. 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?
    Tomás Ó hÉilidhe, Oct 22, 2008
    #1
    1. Advertising

  2. Tomás Ó hÉilidhe

    Ian Collins Guest

    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
    Ian Collins, Oct 22, 2008
    #2
    1. Advertising

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


    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
    Peter Nilsson, Oct 22, 2008
    #3
  4. Tomás Ó hÉilidhe

    Guest

    In article <>,
    Tomas S hIilidhe <> 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:


    [...]


    > 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, 2008
    #4
  5. On Oct 23, 3:47 am, Peter Nilsson <> wrote:

    > > 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".
    Tomás Ó hÉilidhe, Oct 22, 2008
    #5
  6. On Oct 23, 4:02 am, (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
    Tomás Ó hÉilidhe, Oct 22, 2008
    #6
  7. 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;
    }
    Martin Ambuhl, Oct 22, 2008
    #7
  8. Tomás Ó hÉilidhe

    Ian Collins Guest

    Tomás Ó hÉilidhe wrote:
    > On Oct 23, 3:47 am, Peter Nilsson <> wrote:
    >
    >>> 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
    Ian Collins, Oct 22, 2008
    #8
  9. On Oct 23, 4:34 am, Martin Ambuhl <> wrote:

    >      scanf("%u", &siblings);     /* mha: note change */



    Did you mean to write?:

    "%u\n"
    Tomás Ó hÉilidhe, Oct 22, 2008
    #9
  10. Tomás Ó hÉilidhe <> writes:

    > On Oct 23, 3:47 am, Peter Nilsson <> wrote:
    >
    >> > 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.
    Nate Eldredge, Oct 22, 2008
    #10
  11. 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.
    Tomás Ó hÉilidhe, Oct 23, 2008
    #11
  12. Tomás Ó hÉilidhe

    Guest

    Tomás Ó hÉilidhe wrote:
    > On Oct 23, 3:47�am, Peter Nilsson <> wrote:
    >
    > > > 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 23, 2008
    #12
  13. Tomás Ó hÉilidhe

    CBFalconer Guest

    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.
    CBFalconer, Oct 23, 2008
    #13
  14. Tomás Ó hÉilidhe <> writes:

    > 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.
    Nate Eldredge, Oct 23, 2008
    #14
  15. Nate Eldredge <> writes:
    [...]
    > 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) <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"
    Keith Thompson, Oct 23, 2008
    #15
  16. Keith Thompson <> writes:

    > Nate Eldredge <> writes:
    > [...]
    >> 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') ;
    Nate Eldredge, Oct 23, 2008
    #16
  17. 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?
    Tomás Ó hÉilidhe, Oct 23, 2008
    #17
  18. On Oct 23, 12:01 am, Tomás Ó hÉilidhe <> wrote:
    > 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.
    Barry Schwarz, Oct 23, 2008
    #18
  19. On 22 Oct, 23:56, Nate Eldredge <> wrote:
    > Tomás Ó hÉilidhe <> writes:


    <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
    Nick Keighley, Oct 23, 2008
    #19
  20. Tomás Ó hÉilidhe

    Flash Gordon Guest

    Nate Eldredge wrote, On 22/10/08 23:56:
    > Tomás Ó hÉilidhe <> writes:


    <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
    If emailing me use my reply-to address
    See the comp.lang.c Wiki hosted by me at http://clc-wiki.net/
    Flash Gordon, Oct 23, 2008
    #20
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. bruce varley
    Replies:
    2
    Views:
    382
    Mike Wahler
    Nov 25, 2003
  2. Replies:
    2
    Views:
    432
    Larry I Smith
    May 29, 2005
  3. Replies:
    9
    Views:
    660
    Michael Wojcik
    Aug 23, 2005
  4. Replies:
    9
    Views:
    610
    Alex Buell
    Apr 27, 2006
  5. Kashif Ur Rehman
    Replies:
    2
    Views:
    853
    Tom Hawtin
    May 17, 2007
Loading...

Share This Page