在正式解说scanf缓冲区之前,我们先吃个开胃小菜.
Q: 为什么调用scanf函数线程会停住等待输入?
A: scanf会调用read系统调用获取用户输入的信息, 在没有准备好输入数据时, read系统调用会进入等待状态.
scanf
__svfscanf_l
__srefill
__srefill1
_sread
_read(read系统调用)
__svfscanf_l:
__srefill1会初始化输入缓冲区buffer的数据.
scanf输入缓冲区和printf输出缓冲区都在FILE结构体, scanf主要关注在struct __sbuf _bf(整个缓冲区)和unsigned char *_p(当前缓冲位置).
参考: printf 缓冲区原理 (你想知道的C语言 1.4)
Q: 如何dump输入缓冲区数据?
A: 一样可以从FILE结构体_p成员获取缓冲数据.
/*
Xi Chen([email protected])
cxsjabcabc
*/
#include
#include
#include
#include
void check_stdin_buffer()
{
FILE *fp = stdin;
unsigned char *p;
int size;
p = stdin->_p;
size = p ? strlen((const char *)p) : 0;
write(1, "stdin buffer:", strlen("stdin buffer:"));
write(1, p, size);
write(1, "\n", 1);
}
int main(int argc, char *argv[])
{
int n;
// stdin buffer should be empty now!
check_stdin_buffer();
// input one integer
scanf("%d", &n);
// current input buffer should be the extra string getting rid of the previous int
check_stdin_buffer();
// set the current input buffer character to 'X'
stdin->_p[0] = 'X';
check_stdin_buffer();
return 0;
}
https://github.com/cxsjabc/basic/blob/dev/c/_topics/scanf/stdin_buffer.c
运行结果:
Note: 输入123ZY, scanf %d吃掉123 3个字符, 缓冲区剩下ZY, 强制设置缓冲区第一个字符为X, 当前缓冲区为XY.
Q: scanf默认是行缓冲, 可以输入1个字符就返回吗?
A: 这需要设定TTY模式, 专业名词叫termios, 正因为对于终端输入输出, 不同用户可能有不同要求:
termios才格外重要!
/*
Xi Chen([email protected])
cxsjabcabc
*/
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
struct termios t;
char buf[5] = {0};
/* set tty buffer as: max one character */
tcgetattr(0, &t);
t.c_lflag &= ~(ICANON);
t.c_cc[VMIN] = 1;
tcsetattr(0, TCSANOW, &t);
tcgetattr(0, &t);
printf("VMIN:%d, VTIME:%d\n", t.c_cc[VMIN], t.c_cc[VTIME]);
// if input one char, read will return!
read(0, buf, 4);
printf("buffer: %s\n", buf);
return 0;
}
一旦输入任何有效字符, read就会立即返回, 不会等到行结束符.
Q: 更进一步, 当输入缓冲区没数据, 可以根本不等待就返回吗?
A: 可以直接修改上面程序t.c_cc[VMIN] = 1 改为 0.
另外,也可以利用NON_BLOCK标志位:
/*
Xi Chen([email protected])
cxsjabcabc
*/
#include
#include
#include
int main(int argc, char *argv[])
{
int ret;
char buf[16] = {0};
int fd;
// stdin as non block
fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
if (fd < 0) {
printf("open tty error!\n");
return -1;
}
// if no input data, return immediately!
ret = read(fd, buf, 15);
if (ret > 0)
printf("|%s|\n", buf);
else
printf("no avaiable data!\n");
close(fd);
return 0;
}
程序运行发现没输入缓冲数据立即就返回.
TTY read系统调用之内核实作
内核实作非常有趣, 它清晰地展示了如何处理输入数据, 如何回显, 何时从read返回到用户态scanf继续执行, 下回再做分解.
作者: 陈曦
环境: MacOS 10.14.5
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
Linux 3.16.83 (Ubuntu)
转载请注明出处