首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >getchar()和stdin

getchar()和stdin
EN

Stack Overflow用户
提问于 2011-10-12 22:45:26
回答 3查看 35.5K关注 0票数 7

一个相关的问题是here,但我的问题不同。

但是,我想更多地了解getchar()和stdin的内部结构。我知道getchar()最终会调用fgetc(stdin)。

我的问题是关于缓冲、stdin和getchar()行为。给出一个经典的K&R示例:

代码语言:javascript
复制
#include <stdio.h>

main()
{
    int c;

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

在我看来,getchar()的行为可以描述如下:

如果stdin缓冲区中没有任何内容,则让操作系统接受用户输入,直到按enter键。然后返回缓冲区中的第一个字符。

假设程序正在运行,用户输入“anchovies”。

因此,在上面的代码清单中,第一次调用getchar()等待用户输入,并将缓冲区中的第一个字符分配给变量c。在循环中,第一次迭代调用getchar()时会说:“嘿,缓冲区中有东西,返回缓冲区中的下一个字符。”但是while循环的第N次迭代导致getchar()说:“嘿,缓冲区中什么都没有,所以让stdin收集用户输入的内容。

我花了一点时间在c源代码上,但看起来这更像是stdin的行为工件,而不是fgetc()。

我说错了吗?谢谢你的洞察。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2011-10-12 22:58:02

我知道getchar()最终会调用fgetc(stdin)

不一定。getchargetc也可以扩展到从文件中读取数据的实际过程,fgetc实现为

代码语言:javascript
复制
int fgetc(FILE *fp)
{
    return getc(fp);
}

嘿,缓冲区里什么都没有,所以让

收集用户输入的内容。..。看起来这更像是stdin的行为工件,而不是fgetc()

我只能告诉你我所知道的,这就是Unix/Linux的工作原理。在该平台上,FILE (包括stdin所指向的东西)保存一个文件描述符(一个int),该文件描述符被传递给操作系统,以指示FILE从哪个输入源获取数据,外加一个缓冲区和一些其他记账内容。

"gather“部分的意思是”调用文件描述符上的read系统调用,再次填充缓冲区“。不过,这在不同的C实现中是不同的。

票数 4
EN

Stack Overflow用户

发布于 2019-01-23 03:39:57

您所观察到的行为与C和getchar()无关,而是与OS内核中的电传(TTY)子系统有关。

为此,您需要了解进程如何从您的键盘获取输入,以及如何将它们的输出写入您的终端窗口(我假设您使用UNIX,下面的解释特别适用于UNIX,即Linux、macOS等):

上图中标题为“终端”的框是您的终端窗口,例如xterm、iTerm或Terminal.app。在旧时代,由键盘和屏幕组成的分开的硬件设备的终端,它们通过串行线(RS-232)连接到(可能是远程的)计算机。在终端键盘上键入的每个字符都通过这条线路发送到计算机,并由连接到终端的应用程序使用。应用程序作为输出产生的每个字符都通过同一行发送到终端,终端将其显示在屏幕上。

现在,终端不再是硬件设备,而是移动到计算机内部,成为被称为终端仿真器的进程。xterm、iTerm2、Terminal.app等都是终端仿真器。

然而,应用程序和终端仿真器之间的通信机制与硬件终端保持相同的。终端模拟器模拟硬件终端。这意味着,从应用程序的角度来看,今天与终端仿真器(例如iTerm2)对话的功能与1979年与真实终端(例如DEC VT100)对话的功能相同。此机制保持不变,以便为硬件终端开发的应用程序仍可与软件终端仿真器一起工作。

那么这种通信机制是如何工作的呢?UNIX在内核中有一个名为teletype的子系统(TTY代表teletype,这是计算机终端的最早形式,甚至没有屏幕,只有键盘和打印机)。您可以将TTY看作是终端的通用驱动程序。TTY从终端所连接的端口读取字节(来自终端的键盘),并将字节写入该端口(发送到终端的显示器)。

连接到计算机的每个终端(或计算机上运行的每个终端模拟器进程)都有一个TTY实例。因此,TTY实例也被称为TTY device (从应用程序的角度来看,与TTY实例对话就像与终端设备对话)。在使驱动程序接口作为文件可用的UNIX方式中,这些TTY设备以某种形式呈现为/dev/tty*,例如,在macOS上,它们是/dev/ttys001/dev/ttys002等。

应用程序可以将其标准流(stdin、stdout、stderr)定向到TTY设备(实际上,这是默认设置,您可以使用tty命令找出您的shell连接到哪个TTY设备)。这意味着用户在键盘上键入的内容将成为应用程序的标准输入,而应用程序写入其标准输出的内容将被发送到终端屏幕(或终端仿真器的终端窗口)。所有这些都是通过TTY设备实现的,即应用程序只与内核中的TTY设备(这种类型的驱动程序)通信。

现在,关键的一点是: TTY设备不仅仅是将每个输入字符传递给应用程序的标准输入。默认情况下,TTY设备将所谓的线路规程应用于接收到的字符。这意味着,它在本地对它们进行缓冲,并解释delete、backspace和其他行编辑字符,只有在收到回车符或换行符时才将它们传递给应用程序的标准输入,这意味着用户已经完成了整行的输入和编辑。

这意味着在用户点击return之前,getchar()不会在标准输入中看到任何内容。到目前为止,好像什么都没有输入过。只有当用户点击return时,TTY设备才会将这些字符发送到应用程序的标准输入,在那里getchar()会立即将它们读取为。

从这个意义上说,getchar()的行为没有什么特别之处。当stdin中的字符可用时,它会立即读取这些字符。您观察到的行缓冲发生在内核中的TTY设备中。

现在到有趣的部分:这个TTY设备可以被配置。例如,您可以使用stty命令从shell执行此操作。这允许您配置TTY设备应用于传入字符的行规则的几乎所有方面。或者,您可以通过将TTY设备设置为raw模式来禁用任何处理。在这种情况下,TTY设备立即将每个接收到的字符转发到应用程序的标准输入,而不进行任何形式的编辑。

如果您在TTY设备中启用raw模式,您将看到getchar() 会立即接收您在键盘上键入的每个字符。下面的C程序演示了这一点:

代码语言:javascript
复制
#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()从标准输入中读取和打印字符。字符以十六进制和八进制记数法打印为ASCII代码。该程序专门将ETX字符(ASCII码0x03)解释为终止的触发器。您可以通过键入Ctrl-C在键盘上生成此字符。

票数 17
EN

Stack Overflow用户

发布于 2011-10-12 23:31:36

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

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7741930

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档