Why getch() returns before press any key?

2019-01-07 21:29发布

问题:

int main(int argc, char *argv[], char *env[])
{
    printf("Press any key to exit.\n");
    getch();
    return 0;
}

According to the man page,

getch should wait until any key is pressed

...but in fact it returns directly before press any key. (The value returned is -1).

Why?


Update

I'm on Linux. How can I implement Press any key to exit., if not using getch()?

getchar() will only return after press Enter, it's not what I want.

回答1:

On Linux, getch() is probably the curses function, which is quite different from the Windows-specific function of the same name. curses (ncurses) is probably overkill for what you want to do.

The simplest approach would be to wait until the user presses Enter, which you can do like this:

int c;
printf("Press <enter> to quit: ");
fflush(stdout);
while ((c = getchar()) != '\n' && c != EOF) {
    /* nothing */
}

If you really want the user to be able to press any key, not just Enter, you can do something like this:

system("stty cbreak -echo");
getchar();
system("stty cooked echo");

The second stty is intended to restore the tty to reasonable settings (it should really restore them to whatever they were, but saving and restoring the state is a little more complicated). There are probably cleaner ways to do that (using whatever library functions the stty program itself uses).

EDIT: Reading a single character without waiting for Enter is a frequently asked question. In fact, it's question 19.1 in the comp.lang.c FAQ.

EDIT2: I haven't done much work with curses recently, but I just did some playing around with it. It appears that getch() won't work unless you first call initscr() -- and initscr() clears the screen. curses is intended for use with applications like text editors that need full control of the display. There may be a way to use getch() without taking control of the screen, but I haven't found it.

The system("stty ...") kludge might actually be the best approach.

EDIT3: The termios solution in the other answer is probably the best (system("stty ...") is simpler, but invoking an external program feels like overkill.

As for the OP's comment "I can't believe Press any key to exit. is so troublesome to do in c", yes, that does seem odd -- but on further thought there are valid reasons for it.

Take a look at the programs installed on a typical Unix or Linux system. I think you'll find that very few of them require this kind of input (waiting for a single keypress).

A lot of programs work with command line arguments and data read from files or from stdin. Anything the user types is input data, not commands or responses to prompts.

Some programs do ask for confirmation for some actions (installers like apt-get and cpan often do this) -- but they usually read a line of input and check the first character. Or, for some drastic actions, they might require you to type the whole word "yes" followed by Enter (you don't want to reformat your hard drive because you accidentally hit a key).

Of course a lot of programs (text editors, file viewers) read single-character non-echoing input, but such programs tend to be curses-based; they take control of the entire terminal window.

Finally, a number of programs have GUI interfaces (web browsers, etc.); they probably don't even read from stdin.

Most production Unix programs don't use or need Press any key to exit prompts. They just do their jobs, often silently, and then terminate so you can do the next thing. The need exists largely for relatively elementary programs, such as homework assignments. Not that there's anything wrong with homework assignments, but the system as a whole isn't primarily designed to support such usage.



回答2:

Here's a minimal example

#include <curses.h>

int main(){
    initscr();
    cbreak();
    noecho();
    printw("Press any key to continue.");
    refresh();
    getch();
    endwin();
    return 0;
}

But there doesn't seem to be any way to use ncurses without clearing the screen. The Stain of Overkill, perhaps?!

Edit

Here's another way I got working. This does not use curses, nor does it clear the screen.

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

int main() {
    struct termios old,new;

    tcgetattr(fileno(stdin),&old);
    tcgetattr(fileno(stdin),&new);
    cfmakeraw(&new);
    tcsetattr(fileno(stdin),TCSANOW,&new);
    fputs("Press any key to continue.",stdout);
    fflush(NULL);
    fgetc(stdin);
    tcsetattr(fileno(stdin),TCSANOW,&old);

    return 0;
}

The manpage says cfmakeraw() might not be fully portable. But it's just a shorthand for setting a whole mess of flags:

Raw mode
    cfmakeraw() sets the terminal to something like the "raw" mode of the old  Version  7  terminal
    driver:  input  is  available character by character, echoing is disabled, and all special pro-
    cessing of terminal input and output characters is disabled.  The terminal attributes  are  set
    as follows:

       termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                       | INLCR | IGNCR | ICRNL | IXON);
       termios_p->c_oflag &= ~OPOST;
       termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
       termios_p->c_cflag &= ~(CSIZE | PARENB);
       termios_p->c_cflag |= CS8;


标签: c linux getch