【问题标题】:Strange buffering behavior in termios/stdlibtermios/stdlib 中的奇怪缓冲行为
【发布时间】:2022-01-13 18:00:19
【问题描述】:

我正在尝试禁用程序输入/输出的缓冲,以便尽快传输和接收字符,但根据我在 stdio 和termios 文档。这是一个小例子来解释我的意思:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

int trapState;

void sigInt(int blah) {
    trapState++;
    if (trapState == 5) exit(EXIT_FAILURE);
}

int main(int argc, char * argv[]) {
    int b = 0;
    trapState = 0;
    
    signal(SIGINT, &sigInt);
    
    while (1) {
        getchar();
        
        if (trapState > b) puts("boop\n");
        
        b = trapState;
    }
}

直截了当:当你点击 ^C 时,它应该打印一个字符串,然后第五次保释,对吧?

然而,奇怪的是,除非您按 ENTER,否则输出不会显示在终端上;此外,如果您在按 ENTER 之间多次按 ^C,只会打印一条消息, 这意味着 getchar() 一直阻塞到行尾,并且 SIGINT 处理程序正在递增 b 每次迭代不止一次,即使getchar() 在没有输入的情况下应该返回 EOF。 SIGINT 处理程序在它应该运行的时候运行,因为它会在第五次左右立即转储,但程序一直在等待getchar(),直到行缓冲区被填满。

所以,好吧,让我们 A. 尝试解决 stdio 和 B. 上的缓冲问题。考虑多个中断,看看会发生什么:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

int trapState;

void sigInt(int blah) {
    trapState++;
    if (trapState == 5) exit(EXIT_FAILURE);
}

int main(int argc, char * argv[]) {
    int b = 0;
    trapState = 0;
    
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    
    signal(SIGINT, &sigInt);
    
    while (1) {
        while (trapState > b) {
            puts("boop\n");
            b++;
        }
        
        fflush(stdout);
        
        getchar();
    }
}

现在会打印多条消息,但仍仅在按下 ENTER 后才打印,这意味着 getchar() 仍处于阻塞状态,尽管在 stdin 上禁用了缓冲。 (并且我们无法在调用 getchar() 之前检查 EOF,因为 feof() 只能保证在 last 操作之后反映流的状态。)

但 TTY 驱动程序默认设置了行缓冲区。是不是挂在里面了?让我们找出答案:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <termios.h>

int trapState;
struct termios t, z;

void sigInt(int blah) {
    trapState++;
    if (trapState == 5) exit(EXIT_FAILURE);
}

void dump() {
    tcsetattr(fileno(stdin), TCSANOW, &z);
}

int main(int argc, char * argv[]) {
    int b = 0;
    trapState = 0;
    
    setbuf(stdout, NULL);
    setbuf(stdin, NULL);
    
    tcgetattr(fileno(stdin), &z);
    
    tcgetattr(fileno(stdin), &t);
    t.c_lflag &= ~(ICANON);
    t.c_cc[VMIN] = 0;
    t.c_cc[VTIME] = 0;
    tcsetattr(fileno(stdin), TCSAFLUSH, &t);
    fflush(stdout);
    
    signal(SIGINT, &sigInt);
    atexit(&dump);
    
    while (1) {
        while (trapState > b) {
            puts("boop\n");
            b++;
        }
        
        fflush(stdout);
        
        getchar();
        
        tcflush(fileno(stdin), TCIOFLUSH);
    }
}

相同的结果。我有一个 heck 时间来解决这个问题 - getchar() 曾经 从标准输入返回 EOF 吗?或者它只是阻塞直到读取一个字符,无限期地?这种行为似乎是标准的——我在 Linux 系统和 BSD 系统上得到了相同的结果——但根据我正在阅读的文档所说的,它的行为方式并不像我期望的那样。我在这里错过了什么?

【问题讨论】:

  • 您正在阅读的文档是什么?
  • @MC68020 嗯,谢谢;但我还是很好奇。对此的评论者说,不可能通过标准输入来做到这一点,并且“解决方案”代码使用 read() 代替 - 但这不是 stdlib 在后端使用的吗? does getchar() 曾经在标准输入上返回 EOF 吗?
  • getchar() 如果您点击 Ctrl-D 并且没有未读字符,则返回 EOF。否则,第一个 Ctrl-D 返回未读字符,第二个 Ctrl-D 返回 EOF。终端驱动程序允许行编辑(与 BackSpace 一样),因此它不能立即发送字符(BackSpace 不能将它们从进程中吸回)。您必须更改终端模式才能在字符可用时立即获取字符,而 stdio 不支持。
  • getchar缓冲,你可以用setvbuf 改变它;否则你所能做的就是中断对 block 数据的读取。

标签: c terminal io stdin tty


【解决方案1】:

这应该禁用缓冲:

setvbuf(stdout, nullptr, _IONBF, (size_t) 0);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-25
    • 2019-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多