未使用fflush时dup2恢复stdin异常的原因及替代方案
核心问题出在C标准库的stdio缓冲与底层文件描述符不同步:
C的stdio是带缓冲的I/O层,和底层文件描述符(fd)是两个独立的层级。当你用dup2把STDIN_FILENO指向in.txt后,调用scanf("%s")只会读取到第一个空白字符(空格、换行等),如果in.txt里还有剩余内容(比如你例子中的2_line_from_in),这些内容会被留在stdio的输入缓冲区里,而不是直接被底层fd处理。
哪怕你用dup2把STDIN_FILENO恢复成终端的fd,stdio层的缓冲里还残留着之前从in.txt读入的数据。此时第二次scanf会优先读缓冲里的残留内容,而不是去读终端的新输入。
至于fflush(stdin)能“解决”问题,是因为部分编译器(比如GCC的glibc实现)对它做了非标准扩展——清空输入缓冲。但根据C标准,fflush仅对输出流有定义行为,对输入流调用属于未定义行为,不同平台结果可能完全不同,绝对不能依赖。
这里提供三种标准合规的解决方法:
1. 手动清空stdio输入缓冲区
在切换回原stdin之前,把缓冲里的剩余字符全部读取并丢弃,确保缓冲区为空。可以用getchar()循环读取直到EOF或换行(根据你的输入场景选终止条件):
把代码中注释fflush(stdin)的部分替换成:
// 清空stdin缓冲里的剩余字符 int c; while ((c = getchar()) != EOF && c != '\n');
这样就能把in.txt中scanf("%s")没读完的内容全部清掉,后续scanf就会从恢复后的终端fd读取输入。
2. 绕过stdio缓冲,直接用底层系统调用
如果不需要stdio的缓冲功能,直接用read()、write()这类底层系统调用操作文件描述符,完全避开stdio缓冲的干扰:
修改后的示例代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> int main() { int stdin_copy = dup(STDIN_FILENO); int input_fd = open("in.txt", O_RDONLY); dup2(input_fd, STDIN_FILENO); close(input_fd); char input_string[100]; ssize_t bytes_read = read(STDIN_FILENO, input_string, sizeof(input_string)-1); if (bytes_read > 0) { // 去掉换行符(如果存在) input_string[strcspn(input_string, "\n")] = '\0'; printf("String from file: %s\n", input_string); } dup2(stdin_copy, STDIN_FILENO); printf("Enter a string: "); fflush(stdout); bytes_read = read(STDIN_FILENO, input_string, sizeof(input_string)-1); if (bytes_read > 0) { input_string[strcspn(input_string, "\n")] = '\0'; printf("String from terminal: %s\n", input_string); } return 0; }
3. 使用独立的FILE指针
为每个文件描述符创建独立的FILE*对象,避免共用stdin的缓冲。用fdopen打开复制的fd,单独操作:
修改后的示例代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main() { // 保存原stdin的FILE指针 FILE* original_stdin = fdopen(dup(STDIN_FILENO), "r"); if (!original_stdin) { perror("fdopen failed"); exit(1); } int input_fd = open("in.txt", O_RDONLY); FILE* file_stdin = fdopen(input_fd, "r"); if (!file_stdin) { perror("fdopen failed"); exit(1); } char input_string[100]; fscanf(file_stdin, "%s", input_string); printf("String from file: %s\n", input_string); fclose(file_stdin); // 切换回原stdin的FILE指针 stdin = original_stdin; printf("Enter a string: "); fflush(stdout); fscanf(stdin, "%s", input_string); printf("String from terminal: %s\n", input_string); fclose(original_stdin); return 0; }
内容的提问来源于stack exchange,提问作者Peter Žaucer

