read 将 stdout 从无缓冲更改为在规范模式下缓冲的行

read changes stdout from unbuffered to line buffered in canonical mode

当我在规范模式下使用这段代码时:

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

static struct termios newt;
static struct termios oldt;

static void kb_fini(void)
{
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}

void kb_init(void)
{
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= (tcflag_t)~(ICANON | ECHO | ISIG);
    newt.c_cc[VMIN] = 1;
    newt.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    atexit(kb_fini);
}

int main(void)
{
    int c;

    kb_init();
    printf("Press q ");
    c = getchar();
    if (c == 'q') {
        printf("q was pressed\n");
    }
    return 0;
}

在按下 q

之前,我能够在控制台中读取 "Press q "

切换到 read

int main(void)
{
    char c;

    kb_init();
    printf("Press q ");
    read(STDIN_FILENO, &c, 1);
    if (c == 'q') {
        printf("q was pressed\n");
    }
    return 0;
}

在按下 q 之前不显示 "Press q "。

这是为什么?

正如我在 中观察到的,标准 I/O 包知道发生了什么并协调事情,以便在调用之前刷新到标准输出 (stdout) 的未决输出标准输入 (stdin) 上的读取操作——至少当输出和输入是 'interactive device',也就是终端时。请注意,C 标准实际上并未强制要求同步,但大多数实现都提供了它。

read() 系统调用不知道也不关心标准 I/O 程序包发生了什么。它无权访问任何文件流,也无权访问这些流的任何私有数据(例如缓冲输出)。因此,它无法确保在尝试读取输入之前刷新挂起的标准输出。

如果您要混合使用这两种模式,请确保在使用 read() 之前 fflush(stdout);fflush(0);

Does mixing the two modes have well defined behaviour?

这取决于你如何混合它们。如果您使用 stdout 作为输出,使用 STDIN_FILENO 作为输入,除了默认缺少同步之外没有任何问题。如果您尝试将 stdout 操作与直接在 STDOUT_FILENO 上的操作混合,或者 stdin 操作与直接在 STDIN_FILENO 上的操作混合,那么您将陷入一个痛苦的世界,一般来说.不要尝试这样做,因为您重视自己(或您的用户)的理智。在其他问题中,标准 I/O 库可以提前缓冲,文件描述符函数将无法看到什么标准 I/O 已经读取。相反,在写入时,标准 I/O 库将缓冲而文件描述符 I/O 不会。