C++实现cat工具异常:直接按CTRL-D后未输出文件内容的问题
用户实现了一个模拟Linux cat 命令的C++程序,支持通过命令行参数接收多个输入源(用-表示标准输入stdin),将内容复制到标准输出。但执行./ccat - test.txt时,直接按CTRL-D(无输入内容)会导致程序提前退出,不输出test.txt的内容;但如果先输入字符再按CTRL-D,程序就能正常输出test.txt的内容。用户想知道如何修正代码,以及是否需要处理cin、cout等标准流的多实例,若需要该如何实现。
原代码
/**** ccat.cpp ****/ #include <iostream> #include <fstream> #include <string> #include <vector> using namespace std; int main(int argc, char **argv) { if (argc <= 1) { cout << cin.rdbuf(); } else { vector<string> inputs; for (int i=1; i<argc; ++i) inputs.push_back(argv[i]); for (auto &in: inputs) { if (in == "-" || in == "--") { cout << cin.rdbuf(); } else { ifstream *fd = new ifstream(in); if (!fd->is_open()) cerr << "Cannot open file \'" << in << "\'\n"; else cout << fd->rdbuf(); delete fd; } } } return 0; }
测试命令序列
$ ./ccat > test.txt Let's try this text. I would expect a correct behaviour. $ ./ccat - test.txt # 直接按CTRL-D未输入字符 $ ./ccat - test.txt But when I add some characters before hitting CTRL-D... This works fine. But when I add some characters before hitting CTRL-D... This works fine. Let's try this text. I would expect a correct behaviour.
问题原因
当直接按CTRL-D时,cin的EOF标志位被触发,执行cout << cin.rdbuf()会因读取不到数据导致cout的错误标志位(failbit)被置位。C++标准输出流在错误状态下会跳过所有后续输出操作,因此程序虽然会继续遍历到test.txt,但cout << fd->rdbuf()会被直接跳过,最终不输出文件内容。
而先输入字符再按CTRL-D时,cin.rdbuf()能成功读取到数据,cout的状态保持正常,后续输出操作可正常执行。
修复方案
1. 重置流状态
在处理完标准输入后,重置cin和cout的错误状态,清除EOF和错误标志位,确保后续输出操作能正常进行:
if (in == "-" || in == "--") { cout << cin.rdbuf(); // 重置cin和cout的状态,清除异常标志 cin.clear(); cout.clear(); }
2. 避免动态分配流对象
原代码中用new ifstream动态分配文件流,存在内存泄漏风险,改为栈对象更安全:
else { ifstream fd(in); if (!fd.is_open()) { cerr << "Cannot open file \'" << in << "\'\n"; } else { cout << fd.rdbuf(); } }
完整修复后代码
/**** ccat.cpp ****/ #include <iostream> #include <fstream> #include <string> #include <vector> using namespace std; int main(int argc, char **argv) { if (argc <= 1) { cout << cin.rdbuf(); } else { vector<string> inputs; for (int i=1; i<argc; ++i) inputs.push_back(argv[i]); for (auto &in: inputs) { if (in == "-" || in == "--") { cout << cin.rdbuf(); cin.clear(); cout.clear(); } else { ifstream fd(in); if (!fd.is_open()) { cerr << "Cannot open file \'" << in << "\'\n"; } else { cout << fd.rdbuf(); } } } } return 0; }
关于标准流多实例的问题
C++中的cin、cout是全局单实例对象,分别绑定标准输入(文件描述符0)和标准输出(文件描述符1),不需要也不能创建它们的多实例。如果需要操作多个输入/输出源,应该使用ifstream、ofstream或fstream类创建独立的流对象,每个对象对应一个文件或设备。
内容的提问来源于stack exchange,提问作者J-Ro

