为什么 scanf returns 会在按下 Enter 键时控制回到程序?

Why does scanf returns control back to the program on pressing Enter key?

我写了下面的程序。

void main()
{
   int   *piarrNumber1   = (int *) calloc(1, sizeof(int));
   int   iUserInput      = 0;

   scanf("%d", &iUserInput);
   piarrNumber1[(sizeof piarrNumber1 / sizeof(int)) - 1] = iUserInput;
   printf("\n%d\n", piarrNumber1[0]);
}

我在键盘上输入了“3”,然后按了 TAB 键。什么都没发生。然后,我按下 Enter 键。我打印出“3”,程序结束。

如果 "TAB" [Horizanotal Tab] 和 "Enter" [Newline] 都是空白字符,为什么它们的行为不同?

详细信息是特定于操作系统的(因为标准 C99 不了解终端)。

我假设您使用的是 Linux.

首先,stdio(3) is buffering the standard input stream and most other FILE* streams. You might try to change that with setvbuf(3),但这只影响输出缓冲。

更重要的是,当stdin(实际上是它使用的文件描述符,即STDIN_FILENO一般是fileno(stdin)的值)是一个终端(参见isatty(3) to test that), the linux kernel is usually line-buffering the terminal (so called cooked mode) - at least to handle the backspace key. You might change that by switching the tty to raw mode (as every editor like emacs or vim or nano would do). See this question。但是你应该在你的程序退出之前重置熟模式。

所以在正常情况下,会发生两个级别的缓冲:在内核中用于终端的线路规则,在 libc 中用于 stdin

的缓冲

阅读tty demystified page and the Text Terminal HowTo

实际上,如果您想要复杂的终端输入,请使用一些库,例如 ncurses or readline(不要只使用 termios)

另见 stty(1) & termios(3) & tty_ioctl(4); read about ANSI escape codes

请注意,此行缓冲在两个级别(libc 和内核)是特定于 ttys 的。当 stdinpipe(7)(如 echo foo | yourprogram)或文件(如 yourprogram < yourinputfile.txt)时,情况就不同了。

简而言之,ttys 很难理解,因为它们模仿了 1950 至 70 年代复杂而神秘的硬件设备。

大多数 OSes 缓冲键盘输入,以便它们可以正确处理退格键 -- OS 将输入保存在缓冲区中,并且仅在 Enter[ 时将其提供给程序=23=]被击中。

大多数 OSes 也提供了控制它的方法,但是不同的 OSes 的方法是不同的。在 POSIX 系统上,tcsetattr 命令用于控制此终端缓冲以及许多其他内容。您可以阅读 termios(3) 手册页以获取有关它的大量信息。您可以通过设置非规范模式获得所需的行为:

#include <termios.h>
#include <unistd.h>
    :
struct termios attr;
tcgetattr(0, &attr);
attr.c_lflag &= ~ICANON;
tcsetattr(0, TCSANOW, &attr);

这会导致 OS 立即将每个击键发送到您的程序(除了它拦截的一些特殊的,如 ctrl-C),而无需等待输入,也不处理退格键。

请注意,终端设置在使用同一终端的程序之间是持久的,因此您可能希望保存程序启动时的原始设置并在程序退出前恢复它们。