为什么在读取时发现 eof 时设置 failbit?

Why is failbit set when eof is found on read?

提问人:ceztko 提问时间:7/22/2011 最后编辑:ceztko 更新时间:10/2/2020 访问量:11229

问:

我读过早于.忽略异常信息量不大这一事实,我有以下问题:<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)); 

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

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

评论


答:

30赞 templatetypedef 7/22/2011 #1

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

您要询问的特定情况在此处转载:

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

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

如果要执行读取操作,其中最多要读取一定数量的字符,则可以使用成员函数:readsome

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

此函数将读取字符,直到文件末尾,但与此不同的是,如果在读取字符之前到达文件末尾,则不会设置 failbit。换句话说,它说“尝试阅读这么多字符,但如果你不能,这不是一个错误。只要让我知道你读了多少书。这与 形成鲜明对比,后者说“我想要这么多字符,如果你做不到,那就是一个错误。readread

编辑:我忘了提到的一个重要细节是,可以在不触发失败位的情况下设置eofbit。例如,假设我有一个包含文本的文本文件

137

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

ifstream input("myfile.txt");

int value;
input >> value;

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

希望这有帮助!

评论

0赞 ceztko 7/22/2011
不幸的是,这并没有帮助我尝试以有用的方式使用异常。我认为现在大多数框架都不会在阻止读取(示例)时抛出异常,这是我试图实现的。我忘了提到我在二进制模式下使用流,如果它有任何区别,我试图读取的是二进制标头,所以使用>>运算符不是一种选择。
0赞 templatetypedef 7/22/2011
@cetzko-你能详细说明为什么这对你没有帮助吗?您遇到了哪些具体问题?我对 vs 的讨论旨在提供一种避免虚假异常的机制;这不是你要问的吗?readreadsomefailbit
0赞 ceztko 7/22/2011
readsome()据说是非阻塞的。虽然我同意从文件中读取我不会有任何问题,但这是一个假设,在良好的编程中,我不应该这样做。
1赞 ceztko 7/22/2011
这个讨论似乎指出这确实是一个非阻塞的阅读。readsome
2赞 templatetypedef 7/22/2011
啊,我明白了。很抱歉 - 我误读了规范对 .你是对的 - 只会读取已经缓冲的内容,而不必让流缓冲区从设备中提取更多数据。readsomereadsome
2赞 absence 7/22/2013 #2

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

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

评论

0赞 ceztko 3/24/2014
+1:这是一个伟大的发现!它至少有一个缺陷:如果包含的流是 EOF,则 sgetn 的操作不会设置 eofbit。可以通过在 .同样在 Windows 上,它永远不会失败,即使在真正的强制失败的情况下,但这似乎是一个实现问题。我通过更多的研究添加了另一个答案,但你的方法是正确的。peeksgetn
1赞 ceztko 3/24/2014 #3

改进@absence的答案,它遵循一种方法,该方法执行相同的操作,但不对EOF设置failbit。此外,还测试了实际的读取失败,例如通过硬移除 U 盘或网络共享访问中的链接丢弃而中断传输。它已在 Windows 7 和 VS2010 和 VS2013 以及 Linux 和 gcc 4.8.1 上进行了测试。在 linux 上,仅尝试过移除 U 盘。readeof()read()

#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 上,行为更加一致和有意义。如果在实际读取失败时启用异常,它将抛出 .相反,Windows 大多数时候会将读取失败视为 EOF。sgetn()