Skip to main content

Portably get one character from standard input using standard library only

While translating an old Basic game to C, I found myself needing a function to get one character from the keyboard. You can't do this with common standard C library functions like getchar() because the standard input stream is line-buffered (i.e. it will store whole line of input, including the terminating \n, in its internal buffer although getchar() only uses its first character. Subsequent calls to getchar() will consume buffer's remaining characters until exhausted and only then will it resume accepting user's new inputs). This causes problems because if you need inputting twice, the second time you will get some unexpected value instead of letting user entering second character.

Now, I know the way around this is to use operating system calls such as ioctl and read to set input to an unbuffered state and read a character (or better yet use a library like Curses which abstracts away all this in a cross-platform way.) but I started wondering if it was possible to do this entirely via the standard C library.

My first attempt was to empty out the remaining characters in stdin's buffer by adding a call to:

fflush(stdin);

...after calling getchar() but this didn't do anything.

My next attempt was to try and take stdin out of line buffer mode like this:

setvbuf(stdin, NULL, _IONBF, 0);

I had high hopes this would work but actually it seems calling setvbuf on stdin is undefined behavior. It certainly does not work on Linux.

So finally I came up with this function. It works but I have a nagging feeling it could be improved. What do you think?

int getkey(const char* prompt = "") {
    /* Print the prompt message if there is one */
    if (strcmp(prompt, "") != 0) {
        puts(prompt);
    }

    /* Get a character and examine it.  If it is a newline from a previous
       call to this function eat it otherwise put it back in the buffer. */
    int c = getchar();
    if (c != '\n') {
        ungetc(c, stdin);
    }

    /* This is the character we really want. */
    c = getchar();

    /* Drain the input buffer so any extra characters which were pressed are
       discarded except for newline which is needed to actually send the input 
       to stdin. */
    int next;
    while(!feof(stdin) && next != '\n') {  
        next = getchar();
    }

    return c;
}
Jaldhar
  • 409
  • 3
  • 11