getchar()和stdin

jjhzyzn0  于 9个月前  发布在  其他
关注(0)|答案(3)|浏览(63)

一个相关的问题是here,但我的问题不同。
但是,我想知道更多关于getchar()和stdin的内部结构,我知道getchar()最终只是调用fgec(stdin)。
我的问题是关于buffering、stdin和getchar()行为的。举一个经典的K&R例子:

#include <stdio.h>

main()
{
    int c;

    c = getchar();
    while (c != EOF) {
        putchar(c);
        c = getchar();
    }
}

字符串
在我看来,getchar()的行为可以描述如下:
如果stdin缓冲区中没有任何内容,让操作系统接受用户输入,直到按下[enter]。然后返回缓冲区中的第一个字符。
假设程序正在运行,用户键入“”。
因此,在上面的代码清单中,对getchar()的第一次调用等待用户输入并将缓冲区中的第一个字符分配给变量c。在循环中,对getchar()的第一次迭代调用说“嘿,缓冲区中有东西,返回缓冲区中的下一个字符。”但是,while循环的第N次迭代导致getchar()说“嘿,缓冲区中没有东西,所以让stdin收集用户输入的内容。
我花了一点时间在c源代码上,但看起来这更像是stdin的行为工件,而不是fgec()。
我错了吗?谢谢你的洞察力。

gdrx4gfi

gdrx4gfi1#

您所观察到的行为与C和getchar()无关,而是与操作系统内核中的电传打字(TTY)子系统有关。
为此,您需要了解进程如何从键盘获取输入,以及它们如何将输出写入终端窗口(我假设您使用UNIX,以下解释特别适用于UNIX,即Linux,macOS等):
x1c 0d1x的数据
上图中标题为“终端”的框是您的终端窗口,例如xterm,iTerm或Terminal. app。在旧时代,终端中有单独的硬件设备,包括键盘和屏幕,他们与一个通过串行线(RS-232)连接(可能是远程)计算机。在终端键盘上键入的每个字符都通过这条线发送到计算机,并由连接到终端的应用程序使用。应用程序生成的每个字符都是输出通过同一线路发送到终端,终端将其显示在屏幕上。
如今,终端不再是硬件设备,而是移动到计算机“内部”,成为被称为终端仿真器的进程。xterm、iTerm 2、Terminal.app等都是终端仿真器。
然而,应用程序和终端模拟器之间的通信机制与硬件终端保持相同。终端模拟器模拟硬件终端。这意味着,从应用程序的Angular 来看,我今天在跟一个终端模拟器聊天(例如iTerm2)的工作方式与与真实的终端对话相同这种机制保持不变,以便为硬件终端开发的应用程序仍然可以与软件终端仿真器一起工作。
那么,这种沟通机制是如何运作的呢?UNIX在内核中有一个名为TTY的子系统(TTY代表电传打字机,这是最早的计算机终端形式,甚至没有屏幕,只有键盘和打印机)。您可以将TTY视为终端的通用驱动程序。TTY从终端连接的端口读取字节(来自终端的键盘),并将字节写入此端口(发送到终端的显示器)。
每个连接到计算机的终端都有一个TTY示例(或计算机上运行的每个终端模拟器进程)。因此,TTY示例也称为TTY设备(从应用程序的Angular 来看,与TTY示例对话就像与终端设备对话)。在使驱动程序接口作为文件可用的UNIX方式中,这些TTY设备以某种形式出现为/dev/tty*,例如,在macOS上它们是/dev/ttys001/dev/ttys002等。
应用程序可以有其标准流(stdin,stdout,stderr)定向到TTY设备(实际上,这是默认的,你可以用tty命令找到你的shell连接到哪个TTY设备)这意味着无论用户在键盘上键入什么都会成为应用程序的标准输入,应用程序写入其标准输出的任何内容都将发送到终端屏幕(或终端模拟器的终端窗口)。所有这些都通过TTY设备发生,也就是说,应用程序只与内核中的TTY设备(这种类型的驱动程序)通信。
现在,关键的一点:TTY设备所做的不仅仅是将每个输入字符传递到应用程序的标准输入。默认情况下,TTY设备对接收到的字符应用所谓的行规则。这意味着,它在本地缓冲它们并解释 deletebackspace 和其他行编辑字符,并且仅当它接收到 * 回车 * 或 * 换行 * 时才将它们传递到应用程序的标准输入,这意味着用户已经完成了输入和编辑整行。
这意味着在用户点击 return 之前,getchar()在标准输入中看不到任何内容。就像到目前为止还没有输入任何内容一样。只有当用户点击 return 时,TTY设备才会将这些字符发送到应用程序的标准输入中,getchar()立即将其读取为。
从这个意义上说,getchar()的行为没有什么特别之处。它只是在stdin中的字符变得可用时立即读取它们。您观察到的行缓冲发生在内核的TTY设备中。
现在到有趣的部分:可以配置这个TTY设备。例如,您可以从shell使用stty命令进行配置。这允许您配置TTY设备应用于传入字符的行规则的几乎每个方面。或者您可以通过将TTY设备设置为raw mode来禁用任何处理。在这种情况下,TTY设备立即将每个接收到的字符转发到应用程序的标准输入,而不进行任何形式的编辑。
如果在TTY设备中启用raw模式,您将看到getchar()立即接收您在键盘上键入的每个字符。下面的C程序演示了这一点:

#include <stdio.h>
#include <unistd.h>   // STDIN_FILENO, isatty(), ttyname()
#include <stdlib.h>   // exit()
#include <termios.h>

int main() {
    struct termios tty_opts_backup, tty_opts_raw;

    if (!isatty(STDIN_FILENO)) {
      printf("Error: stdin is not a TTY\n");
      exit(1);
    }
    printf("stdin is %s\n", ttyname(STDIN_FILENO));

    // Back up current TTY settings
    tcgetattr(STDIN_FILENO, &tty_opts_backup);

    // Change TTY settings to raw mode
    cfmakeraw(&tty_opts_raw);
    tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw);

    // Read and print characters from stdin
    int c, i = 1;
    for (c = getchar(); c != 3; c = getchar()) {
        printf("%d. 0x%02x (0%02o)\r\n", i++, c, c);
    }
    printf("You typed 0x03 (003). Exiting.\r\n");

    // Restore previous TTY settings
    tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_backup);
}

字符串

程序将当前进程的TTY设备设置为raw模式,然后使用getchar()在循环中从stdin读取和打印字符。字符以十六进制和八进制表示法打印为ASCII代码。程序专门将ETX字符(ASCII代码0x03)解释为终止触发器。您可以通过键入Ctrl-C在键盘上生成此字符。

4ioopgfo

4ioopgfo2#

getchar()的输入是行缓冲的,输入缓冲区是有限的,通常是4 kB。你首先看到的是你输入的每个字符的回声。当你按ENTER键时,getchar()开始返回字符,直到LF(转换为CR-LF)。当您连续按下不带LF的键一段时间后,它会在4096个字符后停止回显,你必须按回车键才能继续。

kuuvgm7e

kuuvgm7e3#

我知道getchar()最终只调用fgetc(stdin)
不一定。getchargetc也可以扩展到从文件中阅读的实际过程,fgetc实现为

int fgetc(FILE *fp)
{
    return getc(fp);
}

字符串
嘿,缓冲区里什么都没有,所以让stdin收集用户输入的内容。[.]看起来这更像是stdin的行为工件,而不是fgetc()
我只能告诉你我所知道的,这就是Unix/Linux的工作方式。在那个平台上,FILE(包括stdin指向的东西)保存一个文件描述符(int),该文件描述符被传递给OS以指示FILE从哪个输入源获取数据,加上一个缓冲区和一些其他簿记材料。
然后“gather”部分表示“调用文件描述符上的read系统调用以再次填充缓冲区”。

相关问题