为什么scanf在按Enter键时将控制权返回给程序?

我写了以下程序。

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]都是空白字符,为什么他们的行为不同?

大多数操作系统缓冲键盘输入,以便它们可以正确处理退格 – 操作系统将输入保持在缓冲区中,并且仅在Enter命中时将其提供给程序。

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

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

这会导致操作系统立即向你的程序发送每个击键(除了一些特殊的击键,例如ctrl-C ),无需等待Enter ,也没有处理退格。

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

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

我假设您使用的是Linux

首先, stdio(3)缓冲标准输入流和大多数其他FILE*流。 您可能尝试使用setvbuf(3)更改它,但这仅影响输出缓冲。

更重要的是,当stdin (实际上是它使用的文件描述符,即STDIN_FILENO ,通常是fileno(stdin)的值)是一个终端(参见isatty(3)来测试),linux内核通常是行缓冲的终端(所谓的熟化模式 ) – 至少处理退格键。 您可以通过将tty切换到原始模式来更改它(因为像emacsvimnano这样的每个编辑器都可以)。 看到这个问题 。 但是你应该在程序退出之前重置熟化模式。

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

阅读tty揭秘页面和文本终端HowTo

在实践中, 如果你想要复杂的终端输入,使用像ncurses或readline这样的 (不要只使用termios)

另见stty(1) & termios(3) & tty_ioctl(4) ; 阅读ANSI转义码 。

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

简而言之,ttys很难理解,因为它们模仿了20世纪50年代到70年代的复杂和神秘的硬件设备。