在正式解說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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
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, 正因為對于終端輸入輸出, 不同使用者可能有不同要求:
- 需要不等待即傳回;
- 緩沖區1個字元就傳回;
- 輸入的字元不需要回顯;
- '\r'需要被當做'\n';
- ....
termios才格外重要!
/*
Xi Chen([email protected])
cxsjabcabc
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
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 <stdio.h>
#include <unistd.h>
#include <fcntl.h>
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)
轉載請注明出處