【问题标题】:Why is failbit set when eof is found on read?为什么在读取时发现 eof 时会设置故障位?
【发布时间】:2023-03-10 01:13:02
【问题描述】:

我听说<fstream> 早于<exception>。忽略fstream 上的异常信息不是很丰富的事实,我有以下问题:

可以使用exceptions() 方法对文件流启用异常。

ifstream stream;
stream.exceptions(ifstream::failbit | ifstream::badbit);
stream.open(filename.c_str(), ios::binary);

任何尝试打开不存在的文件、没有正确权限的文件或任何其他 I/O 问题都将导致异常。使用自信的编程风格非常好。该文件应该在那里并且是可读的。如果条件不满足,我们会得到一个异常。如果我不确定文件是否可以安全打开,我可以使用其他函数来测试它。

但现在假设我尝试读入缓冲区,如下所示:

char buffer[10];
stream.read(buffer, sizeof(buffer)); 

如果流在填充缓冲区之前检测到文件结尾,则流决定设置failbit,如果启用,则会引发异常。为什么?这有什么意义?我本可以验证只是在阅读后测试eof()

char buffer[10];
stream.read(buffer, sizeof(buffer));
if (stream.eof()) // or stream.gcount() != sizeof(buffer)
    // handle eof myself

这种设计选择阻止我在流上使用标准异常,并迫使我创建自己的权限或 I/O 错误异常处理。还是我错过了什么?有什么出路吗?例如,我是否可以在这样做之前轻松测试是否可以读取流上的sizeof(buffer) 字节?

【问题讨论】:

    标签: c++ fstream eof


    【解决方案1】:

    failbit 旨在允许流报告某些操作未能成功完成。这包括诸如无法打开文件、尝试读取不存在的数据以及尝试读取错误类型的数据等错误。

    您所询问的具体案例在此转载:

    char buffer[10];
    stream.read(buffer, sizeof(buffer)); 
    

    您的问题是为什么在读取所有输入之前到达文件结尾时设置了失败位。原因是这意味着读取操作失败 - 您要求读取 10 个字符,但文件中没有足够多的字符。因此,操作没有成功完成,并且流信号 failbit 让您知道这一点,即使将读取可用的字符。

    如果您想进行读取操作,您希望读取最多个字符,您可以使用readsome 成员函数:

    char buffer[10];
    streamsize numRead = stream.readsome(buffer, sizeof(buffer)); 
    

    此函数将读取字符直到文件末尾,但与read 不同,如果在读取字符之前到达文件末尾,它不会设置故障位。换句话说,它说“试着读这么多字符,但如果你不能读也不是错误。只要让我知道你读了多少。”这与 read 形成鲜明对比,read 表示“我想要精确地这么多字符,如果你做不到,那就是错误。”

    编辑:我忘记提到的一个重要细节是可以设置 eofbit 而不触发 failbit。例如,假设我有一个包含文本的文本文件

    137
    

    之后没有任何换行符或尾随空格。如果我写这段代码:

    ifstream input("myfile.txt");
    
    int value;
    input >> value;
    

    此时input.eof() 将返回true,因为当从文件中读取字符时,流会到达文件末尾,试图查看流中是否还有其他字符。但是,input.fail()不会返回 true,因为操作成功了 - 我们确实可以从文件中读取一个整数。

    希望这会有所帮助!

    【讨论】:

    • 不幸的是,这并没有帮助我尝试以有用的方式使用异常。我认为现在大多数框架不会在阻塞读取(example)时抛出异常,这是我试图实现的。我忘了提到我在二进制模式下使用流,如果它有任何不同的标题,而我试图读取的是二进制标题,所以使用 >> 运算符不是一个选项。
    • @cetzko- 你能详细说明为什么这对你没有帮助吗?您遇到什么具体问题?我对readreadsome 的讨论旨在提供一种机制来避免虚假的failbit 异常;这不是你要问的吗?
    • readsome() 据说是非阻塞的。虽然我同意从文件中读取不会有任何问题,但这是一个假设,在良好的编程中我不应该这样做。
    • This 讨论似乎指出readsome 确实是非阻塞读取。
    • 啊,我明白了。抱歉 - 我误读了规范对 readsome 的描述。你是对的 - readsome 只会读取已经缓冲的内容,而不必让流缓冲区从设备中提取更多数据。
    【解决方案2】:

    直接使用底层缓冲区似乎可以解决问题:

    char buffer[10];
    streamsize num_read = stream.rdbuf()->sgetn(buffer, sizeof(buffer));
    

    【讨论】:

    • +1:这是一个伟大的发现!它至少有一个缺陷:如果包含流是 EOF,则 eofbit 不会被 sgetn 的操作设置。可以通过在sgetn 之后立即在流上执行peek 来考虑它。同样在 Windows 上它永远不会失败,即使在真正的强制失败的情况下,但这似乎是一个实现问题。我通过更多研究添加了另一个答案,但您的方法是正确的。
    【解决方案3】:

    改进@absence 的答案,它遵循readeof() 的方法,该方法与read() 相同,但没有在EOF 上设置故障位。还测试了真正的读取失败,例如硬移除 USB 记忆棒或网络共享访问中的链接丢失导致传输中断。它已经在带有 VS2010 和 VS2013 的 Windows 7 以及带有 gcc 4.8.1 的 linux 上进行了测试。在 linux 上,只尝试过移除 USB 记忆棒。

    #include <iostream>
    #include <fstream>
    #include <stdexcept>
    
    using namespace std;
    
    streamsize readeof(istream &stream, char *buffer, streamsize count)
    {
        if (count == 0 || stream.eof())
            return 0;
    
        streamsize offset = 0;
        streamsize reads;
        do
        {
            // This consistently fails on gcc (linux) 4.8.1 with failbit set on read
            // failure. This apparently never fails on VS2010 and VS2013 (Windows 7)
            reads = stream.rdbuf()->sgetn(buffer + offset, count);
    
            // This rarely sets failbit on VS2010 and VS2013 (Windows 7) on read
            // failure of the previous sgetn()
            (void)stream.rdstate();
    
            // On gcc (linux) 4.8.1 and VS2010/VS2013 (Windows 7) this consistently
            // sets eofbit when stream is EOF for the conseguences  of sgetn(). It
            // should also throw if exceptions are set, or return on the contrary,
            // and previous rdstate() restored a failbit on Windows. On Windows most
            // of the times it sets eofbit even on real read failure
            (void)stream.peek();
    
            if (stream.fail())
                throw runtime_error("Stream I/O error while reading");
    
            offset += reads;
            count -= reads;
        } while (count != 0 && !stream.eof());
    
        return offset;
    }
    
    #define BIGGER_BUFFER_SIZE 200000000
    
    int main(int argc, char* argv[])
    {
        ifstream stream;
        stream.exceptions(ifstream::badbit | ifstream::failbit);
        stream.open("<big file on usb stick>", ios::binary);
    
        char *buffer = new char[BIGGER_BUFFER_SIZE];
    
        streamsize reads = readeof(stream, buffer, BIGGER_BUFFER_SIZE);
    
        if (stream.eof())
            cout << "eof" << endl << flush;
    
        delete buffer;
    
        return 0;
    }
    

    底线:在 linux 上,行为更加一致和有意义。在实际读取失败时启用异常,它将抛出sgetn()。相反,Windows 大多数时候会将读取失败视为 EOF。

    【讨论】:

      猜你喜欢
      • 2018-11-12
      • 2019-10-28
      • 2013-07-27
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多